Posted on

The Illustrated Guide to SSH Port Forwarding

SSH is a powerful tool for accessing remote systems. This guide will illustrate one of the more confusing and poorly documented capabilities of the ssh command on Linux: port forwarding. Port forwarding is a way to “tunnel” any TCP protocol through a secure, encrypted SSH connection. It can also be used to make network connections transparent to the applications that are using them. The diagram below shows a user with an application running on a local machine (Client), such as a laptop. The app needs to interact with a server hosted on a remote host (Protected) which is isolated behind a login node (Login). This situation may occur when a user wants to run a management or admin GUI for a database such as MySQL or MongoDB. In a production environment, a database server is never exposed directly to the internet. Database connections on a private network are often unencrypted to maximize speed. SSH port forwarding can be used to connect the GUI to a database on a remote server. Forwarding is also used for running visualization applications on a GPU node that is located behind the login node on a high-performance computing cluster.

SSH Port Forwarding

Obviously, the user must be able to log in on both of the remote hosts (Protected and Login). Two SSH commands are required: the first command is run on the client machine to establish forwarding to Login, which also starts an interactive session on Login. The -L flag sets up forwarding from a “local” host to a “remote” host (note: the “local” host is the host on which the SSH command is run).

ssh -L 27017:localhost:27017 username@Login

The syntax of the -L flag is (sort of) explained in the ssh man page. Refer to the diagram above for the meaning of these terms:

[bind_address:]port:host:hostport

The -L flag sets up forwarding so that any traffic directed to localhost:27017 on Client (such as a MongoDB GUI) will be forwarded to localhost:27017 on Login. The bind_address option is used if you want to forward traffic from an address on Client other than localhost.

Now, we need to run an SSH command on Login to establish forwarding to the Protected host:

ssh -L 27017:localhost:27017 username@Protected

This is confusing, because it’s almost the same command! Why does it work? On Login, packets are being directed to localhost:27017. From the Login perspective, that traffic might as well be coming from a local application. Therefore, we need to forward localhost:27017 on Login to localhost:27017 on the Protected host.

Now, when we run our local application on Client after setting up forwarding, we connect to localhost:27017, NOT to a remote server! Applications on Client think they are talking to a local database. Likewise, the DB server on Protected thinks it’s talking to an application running on Protected. SSH port forwarding makes the intervening network links transparent to the applications.

Confusion alert: there is another ssh option, -R, which sets up forwarding in the opposite direction: it forwards from the remote host to the local host. The definitions of host, hostport, port, and bind_address are reversed when the -R option is used!