SSH tunneling (or port forwarding) is a great feature for facilitating all kinds of network shenanigans. You can bounce network traffic all around the internet through an encrypted SSH tunnel. Need to connect to a service bound to localhost on a remote machine? No problem. Want to share access to a service only available on your LAN? Easy. SSH tunneling can do all of this and more.

The only downside is figuring out how to configure the tunnel you need. While the command syntax is initially off-putting, we can figure out what we need with this simple mnemonic that breaks tunnel configuration down to three steps. The only assumption is we have an ssh client (local), and an ssh server (remote).

  1. Which host should the tunnel entrance be on? Where are the users of your tunnel going to be connecting from? Are you connecting to your local machine to tunnel to a remote service? If so, you want “Local port forwarding”, and the -L flag. If your users enter the tunnel from the remote end (your sharing access to a webserver on your LAN behind a NAT), you want “Reverse port forwarding”, and the -R flag.

  2. Which address and port do we want the entrance of the tunnel to be attached to? From the perspective of the tunnel entrance host, which port will users send traffic to to get it into the tunnel and “forwarded” to the tunnel destination.

  3. Which address and port do we want to send forwarded data to? From the perspective of the tunnel exit host, which port will that host send traffic to when it gets to the end of the tunnel?

local remote comparison

Pretty simple.

So let’s try it.

Local SSH Port Forwarding

  1. I want to be able to connect locally and be tunneled to remote webserver (-L).
  2. I want to connect to port 8080 on my loopback device to get to the tunnel (localhost:8080).
  3. I want to traffic forwarded to port 80 on the remote hosts’s loopback address (localhost:80).
local:~$ ssh -L locahost:8080:localhost:80 user@remote

local remote comparison

Now a remote example.

Remote SSH Port Forwarding

  1. I want a user on a remote host to be able to tunnel through my local host (-R).
  2. I want those users to connect to localhost port 8080 on their end (localhost:8080).
  3. I want the tunnel to come out and point to another host on my LAN (webserver), which is running a webserver accessible to any hosts on the same subnet, which our local host is. (webserver:80)
local:~$ ssh -R localhost:8080:webserver:80 user@remote

local remote comparison

You technically don’t have to specify the initial bind address (the first occurrence of “localhost” in both examples), but I think it makes things more clear. Generally you’ll be using “localhost” unless you want other hosts to be able to use your tunnel entrance. You can specify a blank host, or a “*” to bind to all available interfaces. This behavior can be governed by the SSH server with the GatewayPorts config directive.