Port forwarding using SSH tunnels is a convenient way to circumvent
well-intentioned firewall rules, or to access resources on otherwise
unaddressable networks, particularly those behind NAT (with addresses such as
However, it has a shortcoming in that it only allows us to address a specific host and port on the remote end of the connection; if we forward a local port to machine A on the remote subnet, we can’t also reach machine B unless we forward another port. Fetching documents from a single server therefore works just fine, but browsing multiple resources over the endpoint is a hassle.
The proper way to do this, if possible, is to have a VPN connection into the appropriate network, whether via a virtual interface or a network route through an IPsec tunnel. In cases where this isn’t possible or practicable, we can use a SOCKS proxy set up via an SSH connection to delegate all kinds of network connections through a remote machine, using its exact network stack, provided our client application supports it.
Being command-line junkies, we’ll show how to set the tunnel up with
to retrieve resources on it via
curl, but of course graphical browsers
are able to use SOCKS proxies as well.
As an added benefit, using this for browsing implicitly encrypts all of the traffic up to the remote endpoint of the SSH connection, including the addresses of the machines you’re contacting; it’s thus a useful way to protect unencrypted traffic from snoopers on your local network, or to circumvent firewall policies.
Establishing the tunnel
First of all we’ll make an SSH connection to the machine we’d like to act as a SOCKS proxy, which has access to the network services that we don’t. Perhaps it’s the only publically addressable machine in the network.
$ ssh -fN -D localhost:8001 remote.example.com
In this example, we’re backgrounding the connection immediately with
explicitly saying we don’t intend to run a command or shell with
only interested in establishing the tunnel.
Of course, if you do want a shell as well, you can leave these options out:
$ ssh -D localhost:8001 remote.example.com
If the tunnel setup fails, check that
AllowTcpForwarding is set to
/etc/ssh/sshd_config on the remote machine:
Note that in both cases we use
localhost rather than
127.0.0.1, in order to
establish both IPv4 and IPv6 sockets if appropriate.
We can then check that the tunnel is established with
ss on Linux:
# ss dst :8001 State Recv-Q Send-Q Local Address:Port Peer Address:Port ESTAB 0 0 127.0.0.1:45666 127.0.0.1:8001 ESTAB 0 0 127.0.0.1:45656 127.0.0.1:8001 ESTAB 0 0 127.0.0.1:45654 127.0.0.1:8001
Now that we have a SOCKS proxy running on the far end of the tunnel, we can use it to retrieve documents from some of the servers that are otherwise inaccessible. For example, when we were trying to run this from the client side, we found it wouldn’t work:
$ curl http://private.example/contacts.html curl: (6) Couldn't resolve host 'private.example'
This is because the
example subnet is on a remote and unroutable LAN. If its
name comes from a private DNS server, we may not even be able to resolve its
address, let alone retrieve the document.
We can fix both problems with our local SOCKS proxy, by pointing
curl to it
$ curl --proxy socks5h://localhost:8001 http://private.example/contacts.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> <html> <head> <title>Contacts</title> ...
Older versions of
curl may need to use the
$ curl --socks5-hostname localhost:8001 http://private.example/contacts.html
This not only tunnels our HTTP request through to
returns any response, it does the DNS lookup on the other end too. This means
we can not only retrieve documents from remote servers, we can resolve their
hostnames too, even if our client side can’t contact the appropriate DNS server
on its own. This is what the
h suffix does in the
socks5h:// URI syntax
We can configure graphical web browsers to use the SOCKS proxy in the same way, optionally including DNS resolution:
Making things more permanent
If this all works for you and you’d like to set up the SOCKS proxy on the far
end each time you connect, you can add it to your
ssh_config file in
Host remote.example.com DynamicForward localhost:8001
With this done, you should only need to type the hostname of the machine to get a shell and to set up the dynamic forward in the background:
$ ssh remote.example.com