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.

Simple example for local port forwarding with SSH

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 54321:

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 -N:

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”.

Erweitertes Beispiel für Local-Port-Forwarding mit SSH

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.

Example for remote port forwarding with SSH

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 integration server.

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 via http://localhost:8080, although it is operated remotely in an isolated test environment.

OpenSSH Configuration Files

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 ProxyJump or ProxyCommand option in the local SSH configuration (~/.ssh/config) you can elegantly set up even more complex scenarios. In our follow-up article “Tunneling Basics – Part II: OpenSSH Configuration Files” we 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.

Coming up

In another future article we will explain how to set up port forwardings that reconnect automatically after a network failure using systemd user services and autossh on Linux.

–– Translated from German by Julia Bażańska

  1. Because of the shorter spelling and succincter name, we will use “SSH” and “OpenSSH” synonymously in this article, although strictly speaking SSH stands for the concept of encrypted network connections and OpenSSH is the implementation of that concept. All examples in the article are OpenSSH-specific.