The Magic of SSH Tunneling and ProxyJump

There are many cases where a system administrator or user needs to access a resource or system that resides on a remote LAN, often behind a firewall such that the service cannot be accessed directly from the internet. The usual solution to this scenario is to set up a VPN. VPNs are great for secure remote access—don't get me wrong. But there are a few aspects that make them less-than-convenient for many use cases that I encounter on regular basis.

Generally speaking:

  1. VPNs require VPN software to be installed on both server and client (obviously)
  2. VPNs require administrator/root privileges on the client
  3. VPNs require distribution of client configurations and sometimes keys
  4. VPNs modify the client routing table, sometimes rerouting all traffic (often desirable) or introducing a routing conflict with the client's local network (undesirable)
  5. There is a mess of different VPN technologies, vendors, and tools that do not work together. You need different client software and configs to connect to OpenVPN, Cisco, SoftEther, etc. VPNs.

We'll take a look at a few ways to gain comparable access to remote networks, although I'm not going to talk about performance at all here.

SSH Port Forwarding

I see lots of tutorials online about how to use SSH port forwarding, so I'm not going to repeat what's been said. However, it doesn't get enough credit where it's deserved. This is the way I most frequently choose to access remote networks: through dynamic port forwarding, where the SSH client runs a SOCKS proxy server, which can be thought of as a TCP proxy of sorts.


ssh -CD 1080 user@your-remote-server

It's that easy. Now, you can configure your browser to use the SOCKS proxy somewhere in the proxy settings (see that tutorial I linked for Firefox instructions; make sure to enable remote DNS as well). You can additionally remove the local address filter so you can access services on "localhost" that are actually running on the remote machine. Also, there are a number of Chrome/Firefox plugins that allow you to switch proxies at the touch of a button, which I would highly recommend using in order to save your sanity. Generally, my Firefox browser is set to use this SSH proxy by default.

Great, the browser is easy to configure. What about other applications? Enter ProxyChains (also see tsocks), which uses LD_PRELOAD magic to, in simple terms, hook library functions used to make TCP connections. ProxyChains, as the name suggests, allows you to chain multiple proxies together (just one is fine here). It is ideal for pushing things through our SSH tunnel, and can also be used to push things through other services like Tor.

ProxyChains is available through the package manager on most distributions, but I recommend using ProxyChains-ng as it is more recently developed and it will use DNS on your remote system instead of the hardcoded present in the original version.

Once installed, the following in /etc/proxychains.conf is all that is needed to get it working:

socks5 1080

Now, you can use it after running the SSH command above (the following may be proxychains4 depending how it was installed):

$ proxychains curl my-internal-site.lan # needs proxychains-ng in most cases
$ proxychains curl # fetch through the remote server
$ proxychains nc mailserver.lan 25 # talk to a mail server
$ proxychains nmap -sT # Perform remote Nmap scan (no SYN scans)

The first example will only work with ProxyChains-ng since we are using the remote network's local DNS. If you don't have that working, an IP address will suffice instead.

I like to be able to type commands as quickly as possible, so naturally my shell's rc file contains the following shortcuts:

alias pc=proxychains # don't like typing "proxychains"
tun () {ssh -CNPD1080 "$1"} # quickly open tunnel

After reloading the shell, the commands are a bit shorter :)

$ tun & # run in background
$ pc curl




As of OpenSSH 7.3, jumping through one or more SSH hosts has become dead simple. Most of the information on this can be found here, but I'll copy some of the highlights over.

Let's say you have an internal domain that has one public-facing SSH-capable host (, and others that are only on the internal network. We'd add the following to our .ssh/config:

Host *

And that's it. Now, if you run ssh, you will be quietly passed through and will be sitting on the internal server! It's that easy.

Now, let's say you have an additional internal network where the only way in is through, a machine on both the network and your secret internal network, secret.lan. We'd add the following below the above config:

Host *.secret.lan

Note that this will use our previous config in order to reach the machine that is only on the network, and is equivalent to:

Host *.secret.lan

An arbitrary number of hosts can be chained together with commas, each with their own user and/or port defined.

This setup is great if you have a few well-defined networks. But what if a machine doesn't have a resolvable hostname, or you want to jump across some other network? The above-linked wiki page provides this sed-powered config:

Host *+*
        ProxyCommand ssh -W $(echo %h | sed 's/^.*+//;s/^\([^:]*$\)/\1:22/') $(echo %h | sed 's/+[^+]*$//;s/\([^+%%]*\)%%\([^+]*\)$/\2 -l \1/;s/:\([^:+]*\)$/ -p \1/')

Looks crazy, and what it does is crazy too. It allows for chaining of hosts when ssh is invoked:

$ ssh -l user3 user1%host1:2222+user2%host2+host3

Here, % is used as a delimiter for the username. To replicate our above scenario to access a machine on the secret.lan network, we'd simply do:


With the configuration we added for those domains, this is not necessary.

Note that ProxyJump can be accessed when calling SSH through the -J flag.




Chances are, you already have SSH access to a system that is multi-homed on a network you can reach and the one you are trying to remotely access. SSH has a built in capability for tunneling, i.e. the ability to send arbitrary TCP traffic over the encrypted, authenticated SSH connection. Because of this, SSH can more-or-less accomplish the same end result as that of a tunnel VPN (for tap VPNs, different story), and sshuttle was developed to do exactly that. You don't need to configure anything on the remote server; you just need root access on your local machine and SSH access on the remote server with Python installed.

Setting up sshuttle to act like a full VPN is as simple as this:

$ sudo sshuttle --dns -r user@your-remote-server 0/0

This will route all outbound traffic, including DNS requests, through the remote server (0/0 is short for The last parameter can be tweaked to modify what traffic will be routed, in case you only want to use the tunnel for traffic destined to a specific remote network.

Very cool stuff.



Well, there it is. I hope you start incorporating some of these techniques to make your life easier!