Every once in a while I need to set up a temporary SSH tunnel from one computer to another, possibly via a third one, and can’t be bothered with configuring any of my otherwise frequently used GUI tools ‘SSH Tunnel Manager’ for OS X or ‘SSH Tunnel’ on Win XP. Each time, however, I end up reading the man page for the ssh command or googling for ‘ssh tunnel linux’ or something similar, since I never seem to remember how to set up a simple SSH tunnel on the command line. The ssh man page is especially unhelpful:
What? Which port goes where? And what’s the bind_address and hostport? If you read the explanation of the switch you’ll probably just become even more confused. After a few trials and errors I usually get my SSH tunnel up and running, but most of the time I’m not quite sure what I’ve really done.
So here’s a note to self on how to do this once and for all.
The light in the end of the tunnel
Now, the basic idea is that you issue a normal ssh command to the remote host as if you would open a remote shell, and just throw in the -L switch to instruct ssh to forward a separate port at the same time. Say that I’d like to open up a tunnel from the local machine to http://www.fredrikbostrom.net. The base command is
but since I want to set up a tunnel from, say, port 10001 on the local host to port 3689 on the remote host, I need to throw in the -L switch:
ssh -L 10001:localhost:3689 email@example.com
This is where the confusion sets in. Where did that localhost come from? Intuitively you could think that it refers to the local host, i.e. the machine you’re writing that command on (like the command would say “connect from 10001 on localhost to 3689 on http://www.fredrikbostrom.net”), but no. It refers to the host at the remote side which we want to act as the other end of our tunnel. Since in this case we want the tunnel to end at the same host as we logged in to using the base ssh command, we use ‘localhost’. So in natural language, this would say
Start a tunnel from here (where we write the command) on port 10001, then go to http://www.fredrikbostrom.net as user fredrik and end the tunnel at a host called localhost on port 3689 over there.
Every now and then I use the above command to access the Firefly (mt-daapd) web interface running on my server from my laptop when I’m not at home. And since I haven’t forwarded the 3689 port in my ADSL modem, I have to use an SSH tunnel to access it. The above command does just that and when it’s up and running I can type http://locahost:10001 in my browser, which will access port 3689 on my server. To close the tunnel, just log out from the remote host as you normally would when closing the SSH connection.
Now what if we change ‘localhost’ to something else? For example, I sometimes want to access the MythWeb web interface of my HTPC when I’m not at home. Again, since I haven’t got a forward up and running from my ADSL modem to my HTPC, I have to use an SSH tunnel. This is easily achieved by changing ‘localhost’ in the above command to the hostname of the HTPC:
ssh -L 10001:kermit:80 firstname.lastname@example.org
Again, in natural language, this would say
Start a tunnel from here (where the command is issued) on port 10001, then go to http://www.fredrikbostrom.net as user fredrik and end the tunnel at a host named ‘kermit’ on port 80 over there.
This requires the host kermit to be available in the same local network as the machine you logged in to. If you try to forward to a host not in the same local network, you’ll probably get an error message like
channel 2: open failed: administratively prohibited: open failed.
As I haven’t got any deeper understanding of how the tunneling mechanism actually works, I can’t really say why you can’t do this, but on the other hand, why would you want to?
What then, if you want to open up a tunnel within your local network, when you don’t have a ‘remote side’ per se? In this case you actually have two options. If the local host runs an SSH server, you can ssh into your localhost and make the tunnel end at the final destination host
ssh -L 10001:kermit:80 fredrik@localhost
That’s the command I would use to open up a tunnel from my server (port 10001) to the HTPC (port 80) while in a shell on the server. This is possible since the server (now the local host) runs an SSH server that I can log in to. It might however seem awkward since I essentially open up an SSH shell connection to the same computer I’m issuing the command on, and the tunnel to kermit is created sort of as an invisible ‘side-effect’.
The other option comes in handy when the local host doesn’t have an SSH server running. This command is exactly the same as the first tunnel command we saw, namely
ssh -L 10001:localhost:80 fredrik@kermit
If issued on my laptop while I’m at home, this would open up a tunnel from port 10001 on my laptop to port 80 on my HTPC. And in this case, I would end up in a shell on kermit.
Send your stooges to dig the tunnel
As mentioned above, these commands not only create a tunnel to the desired end machine, they also open up a normal shell connection to the remote host as they would without the -L switch. This is convenient if you don’t need your local shell or want to do some work at the remote side while you’re at it, since you can close the tunnel just by logging out from the remote side.
If, however, you need your local shell and can’t be bothered opening a new terminal or for some other reason would just want to open up the tunnel and forget about it, you can send it to the background using the -f and -N switches in addition to the -L switch.
ssh -fNL 10001:kermit:80 email@example.com
This will not open the remote shell but instead send the tunnel process to the background. The downside is that you’ll have to look up the process and kill it manually when you want to close the tunnel.