ASCII Thoughts

SSH Local Tunneling

What is tunneling? To put it simply, tunneling is the mechanism by which one port on your local machine redirects to another port on another machine. To understand what it means and why it's useful, let's look at a few use cases.

Local forward, simple case

Imagine you are working on a remote server, example.com. This server has a pretty standard configuration: Varnish, Apache, ...

Let's now say that for some reason you are having troubles with the web server but you are not sure whether the problem is coming from Varnish or from the application itself. So you need to access Apache, but since Apache is listening on localhost, you can't just go to http://example.com:8000, you won't be able to connect. Here comes SSH tunneling to the rescue:

ssh -L 8000:127.0.0.1:8000 example.com

What does this do? It creates a tunnel between the local port 8000 on your machine to the IP address 127.0.0.1 and port 8000 on the remote machine. So, when you go to http://127.0.0.1:8000, your connection enters the tunnel and comes out on the other side knocking on Apache's door:

Browser -> 127.0.0.1:8000 -> SSH --+  # your machine
                                   |
Apache <-  127.0.0.1:8000 <- SSH <-+  # the server

Local forward, binding

The full syntax of the local forward is this :

ssh -L bind:port:host:hostport example.com

By default, SSH will bind to all your network interfaces. So if you are connected to a network, and have an IP address (192.168.0.10 for example), anyone can connect to port 8000 on your machine and access the remote server:

Developer 2                          # other machine
  |
  +--> 192.168.0.10:8000 -> SSH --+  # your machine
                                  |
Apache <- 127.0.0.1:8000 <- SSH <-+  # the server

Maybe the access to this server is sensitive, and you don't want anybody to come and fiddle with it. In this case, you can specify which network interface you want to bind to, and maybe restrict it to localhost:

ssh -L localhost:8000:127.0.0.1:8000 example.com

Local forward, advanced case

Let's go a little further. Imagine now that you want to access a web server located on example2.com. The problem is, example2 is inside a private network and you don't have direct access to it. However, example1 and example2 are on the same network, and you have access to example1. This is the typical situation where you have access to a bridge machine which then gets you access to other machines. This is useful for us because SSH can redirect not only to a local port, but to any port on any machine we have access to on the remote server:

ssh -L 8000:example2.com:80 example1.com

Here is what happens : you enter the tunnel on port 8000 on your machine, get out on machine example1.com and then ask the operating system: "get me to machine example2.com on port 80".

Browser -> 127.0.0.1:8000 -> SSH --+  # your machine
                                   |
         example2.com:80 <- SSH <--+  # example1

With SSH config

You can use aliases for this as well. If you regularly need to forward ports to/from the same host, you can do this in your ~/.ssh/config

Host ex1
  User user1
  Hostname example1.com

Host ex1proxy
  User user1
  Hostname example1.com
  LocalForward 8000 127.0.0.1:80

Which translates to:

ssh ex1       # ssh [email protected]
ssh ex1proxy  # ssh [email protected] -L 8000:127.0.0.1:80

Multiple forwards

You can of course use multiple forwards at the same time. So the following is valid:

ssh [email protected]     \
  -L 8000:127.0.0.1:80    \
  -L 8080:127.0.0.1:8080  \
  -L 8000:example2.com:80

As well as:

Host ex1proxy
  User user1
  Hostname example1.com
  LocalForward 8000 127.0.0.1:80
  LocalForward 8080 127.0.0.1:8080
  LocalForward 8000 example2.com:80

Daemon mode

Finally, if you just want to use SSH to redirect ports, but do not need to launch commands on the remote server, you can do so with -N:

ssh ex1proxy -N &  # launch SSH in daemon mode
[1] 30604          # PID of the process

When you want to kill it:

jobs
[1]+  Running ssh ...  # list background jobs

fg 1                   # bring job to the foreground
^CKilled by signal 2.  # stop it with CTRL+C

Or:

kill 30604
[1]+  Done  ssh ...

Caveats

While I used the same port 8000 for the local and remote machine throughout this post, you don't have to. So the following is just as valid:

ssh -L 9000:127.0.0.1:8000 example.com

Privileged ports still apply. So binding to port 80 for example requires you to sudo:

$ ssh -L 80:127.0.0.1:8000 example.com
Privileged ports can only be forwarded by root.

$ sudo ssh -L 80:127.0.0.1:8000 example.com
Password: ********

# SUCCESS

Conclusion

Tunneling is an invaluable tool in your arsenal, and you should definitely get familiar with it. As usual, you can find more information in the man:

man ssh
man ssh_config

Until next time, Cheers!