manuals:networking:ssh-tunnels

Sometimes, one has to remotely administer servers. And sometimes, those servers run a daemon that has to be configured through a web interface - e.g. CUPS or ejabberd.

In order to connect to such a web interface, one needs a browser, an IP address and a port. In essence, the web interface has to be publicly accessible for that - which is not always possible or desirable. (Firewall configuration, access control, security risks, possibly needing port forwarding, and so on.)

For the basics about SSH, see Access your server using SSH on Justin's website.



One can bind a server's local port to a client's local port using nothing but SSH. This means you only need to use port 22, and you can use SSH pubkey authentication and encryption for everything you do.

ssh [username]@[hostname] -NL [random-local-port]:localhost:[desired-server-port]

For example, if you want to access the CUPS admin page as user debbie, on a print server with hostname lithium and IP address 192.168.0.2, without having to allow TCP traffic on port 631:

The username would be debbie
The hostname would be lithium.local (or 192.168.0.2)
The random local port can be anything, for example 63789
The server port would be 631

That would mean:

ssh debbie@lithium.local -NL 63789:localhost:631

Enter the password (if you don't have pubkey access), press Enter, and go to http://localhost:63789 on the client.

Killing a tunnel is simple: Ctrl + C exits the SSH process.



It is also possible to connect to the web interface of a daemon running on another server in the same LAN as the server you have SSH access to, while the server in question is not remotely accessible.

For example, the SSH server has 192.168.0.50 as its internal IP address. 192.168.0.50:22 will be forwarded to a random port of the external IP address (let's say 90.70.60.50:4444). There is a local user called cindy.

Another server in the LAN has 192.168.0.60 as its internal IP address, has chatserver as its hostname, and runs the ejabberd web interface on the default TCP port 5280.

You can use the same command:

ssh [username]@[external-ip] -p [external-port] -NL [random-local-port]:[desired-server-in-the-lan]:[desired-server-port]

The username would be cindy
The external IP would be 90.70.60.50
The external port would be 4444
The random local port can be anything, for example 5599
The other server in the LAN would be chatserver.local (or 192.168.0.60)
The other server's port would be 5280

That would mean:

ssh cindy@90.70.60.50 -p 4444 -NL 5599:192.168.0.60:5280


Sometimes, one can't get by with mapping only a single port. (Think about SSL redirects, for instance.) In that case, you can use SSH to set up a SOCKS host and tell your browser to connect through that proxy.

You can set up a tunnel by using this command:

ssh -ND [random-local-port] [username]@[external-ip] -p [external-port]

For example, one has an SSH server with local TCP port 22 forwarded to WAN IP address 222.345.111.100, port 8888. There is a local user called jenny.

The username would be jenny
The external IP would be 222.345.111.100
The external port would be 8888
The random local port can be anything, so for example 5599

That would mean:

ssh -ND 5599 jenny@222.345.111.100 -p 8888

You can then tell your browser to use the SOCKS host on localhost:5599

With Firefox, you go to Preferences –> Advanced –> Network –> Settings –> Manual proxy configuration.

With Chromium, the system proxy settings are inherited. So you'll have to set up Network Manager and apply the proxy system-wide.



Sometimes, the remote server is behind NAT and its TCP port 22 is not forwarded to an external port. In that case, you won't be able to connect this way.

Still, there is a solution for this problem. You're going to need some infrastructure on your side though: at least a publicly accessible server running an OpenSSH daemon.

In this example, let's say that you have a server somewhere with IP address 217.77.132.180 and domain name quietlife.nl.

You made a user account called penny and set the shell to /bin/false with sudo usermod -s /bin/false penny.

If Penny were to connect to this server, the session would be terminated immediately as there is no shell for this user. However, it can be very useful as a reverse SSH proxy server.

Connecting to a reverse SSH proxy server

From the remote server (the machine that wasn't able to have its TCP port 22 forwarded), you connect to the proxy server on a random port. (Let's say the remote server has a user called maggie.)

Then, you connect to the proxy server from your computer. From that shell, you connect to localhost on the random port you chose.

First connect to your proxy server from the remote server you want to have remote access to:

ssh -NR [random-port]:localhost:22 [username]@[proxy-server]

The random port can be anything, for example 12345
The username would be penny
The proxy server would be quietlife.nl

That would mean:

maggie@remote-server:~$  ssh -NR 12345:localhost:22 penny@quietlife.nl

Running this command will show nothing interesting. That's exactly what should happen.

On the proxy server, the remote server, with user account maggie, will be connected to port 12345.

If you want to open a secure shell from your proxy server to that remote server:

ssh -N [username]@localhost -p [random-port]

The username would be maggie
The random port would be 12345

That would mean:

penny@proxy-server:~$  ssh -N maggie@localhost -p 12345

Now you'll be logged in to the remote server, even though it's behind NAT and a firewall.



Connecting to a web interface this way, is really hard. You'll need to create two tunnels: one from the remote server to the proxy server, and one from your computer to the proxy server.

For example, you want to access the CUPS admin page on the remote server and tunnel this through the proxy server. The remote server is still connected to port 12345 on the proxy server.

In order to do so, you'll first need a local tunnel from port 631 on the remote server to a random port on the proxy server. Let's use 5678 in this example:

penny@proxy-server:~$  ssh maggie@localhost -p 12345 -NL 5678:localhost:631


From your computer, connect to the proxy server and bind port 5678 (which is forwarded to 631 on the remote server) to another random local port. Let's use 6070 in this example:

abby@computer:~$  ssh penny@quietlife.nl -NL 6070:localhost:5678

This would mean that port 6070 on your computer is forwarded to port 5678 on the proxy server, which in turn is forwarded to port 631 on localhost:12345 on the proxy server, which is actually port 22 on the remote server, meaning that the localhost:631 on the proxy server actually wasn't the local host all along!