Skip to content

published 2023-11-30, updated 2024-01-08


Reverse SSH tunnels for remote port forwarding

Here's how to leverage reverse SSH tunnels to access hosts behind a firewall and/ or (CG)NAT over a public network.

   external host  |          internal network
                  |
  example.com     |	        	 192.0.2.50
  ------------    |   ------------       --------------
  |ssh-server|    |   |ssh-client|       |winrdp:3389 |
  |(ext. IP) |<--ssh--|(int. IP) |--lan--|(int. IP)   |
  ------------    |   ------------       --------------
       A          |         B                   C
    "Broker"            "Jumphost"           "Target"

Configure SSH Servers

To allow (remote) port forwarding, set these two options in /etc/ssh/sshd_config both on Broker (A) and Jumphost (B):

GatewayPorts yes # Allow remote hosts to connect to forwarded ports
AllowTcpForwarding yes # Allow TCP forwarding

Gateway ports can be omitted on Jumphost (B) if all you need is to reach services that are running directly on it.

Establish Tunnel

The ssh-client command is executed on host B ("Jumphost"):

jumphost$ ssh user@example.com -N -R 5000:192.0.2.50:3389
^^^^^^^^           ^^^^^^^^^^^            ^^^^^^^^^^
    B                   A                      C

Access Published Service

Use any independent host that is able to reach Broker (A) and access a service (Windows RDP in this example) running on Target (C):

mstsc /v example.com:5000

If a service (e.g. a local web server) that you want to make reachable is running directly on Jumphost (B), just use localhost instead of Target's (C) IP address:

jumphost$ ssh -N -R 7000:localhost:80 user@example.com

Run tunnels in the background

The example above runs the SSH command in the foreground of your TTY, blocking your session on the jumphost. To continue working on the jumphost, e.g. to spawn more tunnels, consider using the ControlMaster & ControlPath functionality of SSH:

See man ssh_config paragraphs ControlMaster & ControlPath for more information.

Example:

jumphost$ ssh user@example.com -f -M -S ~/session1 -N -R 5000:192.0.2.51:3389
jumphost$ ssh user@example.com -f -M -S ~/session2 -N -R 5000:192.0.2.52:3389

To close the tunnels:

 jumphost$ ssh -S ~/session1 -O exit example.com
 jumphost$ ssh -S ~/session2 -O exit example.com

Instead of using session1 etc. as filenames for the sockets, I like to use the hostnames of my targets.

Use With Caution

Internal services are usually not ment to be publicly reachable.

Firewalling Broker (A) to only allow connections to forwarded ports (:5000) from trusted sources substantially minimizes risks here.

Also may consider to use e.g. a shell script that removes tunnels nightly or something like this..