In software development or system administration, you sometimes want to access services which are not directly accessible from the local network.
In this article we will present the three most common ways of using OpenSSH1 to build tunnels which connect systems that cannot actually talk directly with one other.
Local Port Forwarding
A common use case in software development at cronn GmbH is accessing a database or web service in a test environment.
Normally, the services in the test environment are not directly accessible from the user’s own computer, but require access via a server in the test environment itself, which requires you to log in via SSH.
In our example we want to execute a SQL query on a PostgreSQL database in the test environment. To do this, you could
log on to the database server via SSH and execute it in the command line with
psql to run the SQL query. This
procedure is often too unwieldy in practice and one would much rather work with a database client of your choice (e.g.
DBeaver or DataGrip)
OpenSSH provides a useful tool to achieve exactly that with “local port forwarding”. Some database clients already offer a GUI to enable the connection via port forwarding (aka “SSH tunnel”). However, in this article we want to shed some light on what is actually behind this process and how to establish the connection manually even if, for example, the database client does not offer this possibility.
Let’s assume the database is listening on port
5432 on the database server in our test environment.
With the command
ssh -L 5432:localhost:5432 db.example.com
you can set up a forwarding of port
5432 to your own computer. Once the connection is established, you can use the
database client of your choice to connect to the local port
5432 and execute any SQL queries.
If you already have a local database running on port
5432, then the
ssh-command would fail with the error:
bind [127.0.0.1]:5432: Address already in use channel_setup_fwd_listener_tcpip: cannot listen to port: 5432 Could not request local forwarding.
In practice, local port forwarding is therefore often seen with other free ports such as
ssh -L 54321:localhost:5432 db.example.com
If you run the command, you will notice that next to the port forwarding, OpenSSH also initiates an interactive session
which includes a shell. However, normally you only want to forward the port. You can prevent the creation of this
interactive session with the option
ssh -N -L 54321:localhost:5432 db.example.com
In some cases the database server itself is not accessible via SSH or you lack the necessary permissions. If there is another server in the test environment that can be accessed via SSH and that can connect to the database, we can establish port forwarding through this server. If this server is mainly used to “jump” to other destinations, it is usually called a “jump host” or “jump server”.
In such a case you can establish the connection to the database with the command
ssh -L 54321:db.example.com:5432 jump-host.example.com
If you want to connect to two databases (e.g. a primary and secondary instance), you could start
ssh twice with
different local ports:
ssh -N -L 54321:primary-db.example.com:5432 jump-host.example.com
ssh -N -L 54322:secondary-db.example.com:5432 jump-host.example.com
Alternatively, you can create both port forwardings with just one command:
ssh -L 54321:primary-db.example.com:5432 \ -L 54322:secondary-db.example.com:5432 \ -N jump-host.example.com
All details and options for the command line program
ssh can be found on the ssh man page.
Dynamic Port Forwarding
The creation of local port forwarding can quickly become muddled if you want to connect to a variety of services in the
target environment. For this use case OpenSSH provides dynamic port forwarding. Instead of defining a list of target
systems including ports, one instead specifies a free local port (e.g.
2000), which OpenSSH uses to open
a SOCKS proxy:
ssh -N -D 2000 jump-host.example.com
Via this local SOCKS proxy all systems can be reached as if you were on the jump host yourself. There is, however, one prerequisite: the client must explicitly support connection establishment via a SOCKS proxy. All popular browsers support SOCKS proxies, so dynamic port forwarding can be very handy if you want to connect to, for example, a variety of different web applications that can only be accessed via a jump host.
Remote Port Forwarding
In less frequent cases you may want to set up port forwarding in the opposite direction.
An example use case would be the port forwarding of a service with REST-API, which one would like to use during an
integration test in a continuous integration environment. In this example, we assume that for security reasons you do
not want to allow direct access from the continuous integration server into the test environment. However, the SSH
access is possible from a trusted server (here:
gateway.cloud.com) from the test environment to the continuous
In this case, reverse port forwarding may be set up by creating the forwarding on
gateway.cloud.com with the command
ssh -N -R 8080:service.cloud.com:8080 continuous-integration.example.com
Our software, which is tested on the continuous integration server, can access now the REST service
http://localhost:8080, although it is operated remotely in an isolated test environment.
In some cases, the jump host that you want to use for port forwarding is not directly accessible via SSH, but only with
a jump via another server. With the
ProxyCommand option in the local SSH
~/.ssh/config) you can elegantly set up even more complex scenarios. In a future article we will go
into more detail regarding these options. By doing so we shall be introducing our Java library ssh-proxy,
which makes establishing port forwarding easy in Java applications, e.g. in integrations tests.
–– Translated from German by Julia Bażańska
Because of the shorter spelling and succincter name, we will use “SSH” and “OpenSSH” synonymously in this article, ↩