Jump Hosts -- Passing Through a Gateway or Two
It is possible to connect to another host via one or more intermediaries so that the client can act as if the connection were direct.
There are two basic methods to choose from. The first is to use an SSH connection to port forward the SSH protocol through a jump host (or hosts) to an SSH server running on the target destination host. This is the most secure method because encryption is end-to-end. In addition to whatever other encryption goes on, the end points of the chain encrypt and decrypt each other's traffic. So the traffic passing through the intermediate hosts is always encrypted. But this method cannot be used if either the intermediate hosts or the target host deny port forwarding.
Using the ProxyCommand option to invoke Netcat as the last connection in the chain is variation. The SSH protocol is forwarded by
nc instead of
The second method is to pass the stdout of the SSH connection on the intermediate host to the stdin of the next SSH connection in the chain. The data is decrypted before being re-encrypted and passed on. One implication of this is that the intermediate host must be trusted.
Attention must also be paid to whether or not the username changes from host to host in the chain of SSH connections. The netcat method does not allow a change of username. Other methods do.
When port forwarding is available the easiest way is to use ProxyJump in the configuration file or -J as a run-time parameter. (Of course you may also use the ProxyCommand ssh(1) option to invoke the netcat utility.) An example of -J usage is:
$ ssh -J firewall.example.org:22 server2.example.org
The ProxyJump directive (-J) is so useful it has an entire sub-section below.
In older versions -J is not available. In this case the safest and most straightforward way is to use ssh(1)'s stdio forwarding (-W) mode to "bounce" the connection through an intermediate host.
$ ssh -o ProxyCommand="ssh -W %h:%p firewall.example.org" server2.example.org
This approach supports port-forwarding without further tricks.
Yet older clients don't support the -W option. In this case
ssh -tt may be used. This forces TTY allocation and passes the ssh traffic as though typed. To connect to server2 via firewall as the jump host:
$ ssh -tt firewall.example.com ssh -tt server2.example.org
This opens an SSH terminal to the remote machine. You can also pass commands. For example, to reattach to a remote screen session using screen you can do the following:
$ ssh -tt firewall.example.com ssh -tt server2.example.org screen -dR
The chain can be arbitrarily long and is not limited to just two hosts. The disadvantage of this approach over stdio-forwarding at the network layer with -W or -J is that your session, any forwarded agent, X11 server, or sockets are exposed to the intermediate hosts.
Passing Through One or More Gateways Using ProxyJump
Starting from OpenSSH 7.3, the easiest way to pass through one or more jump hosts is with the ProxyJump directive in ssh_config(5).
Multiple jump hosts can be specified as a comma-separated list. The hosts will be visited in the order listed.
It also has the shortcut of -J when using it as a run-time parameter.
$ ssh -J email@example.com:22 firstname.lastname@example.org
Multiple jump hosts can be chained in the same way.
$ ssh -J email@example.com:22,firstname.lastname@example.org:2222 email@example.com
It is not possible to use both the ProxyJump and ProxyCommand directives in the same host configuration. The first one found is used and then the other blocked.
Old Methods of Passing Through Jump Hosts
In OpenSSH version 7.2 and earlier, passing through one or more gateways is more complex and requires use of stdio forwarding or, prior to 5.4, use of the netcat utility.
Passing Through a Gateway Using stdio Forwarding (Netcat Mode)
Between OpenSSH 5.4 and 7.2 inclusive, a 'netcat mode' can connect stdio on the client to a single port forwarded on the server. This can also be used to connect using ssh(1), but it needs the ProxyCommand option either as a run time parameter or as part of ~/.ssh/config. However, it no longer needs netcat to be installed on the intermediary machine(s). Here is an example of using it in a run time parameter.
$ ssh -o ProxyCommand="ssh -W %h:%p jumphost.example.org" server.example.org
In that example, authentication will happen twice, first on the jump host and then on the final host where it will bring up a shell.
The syntax is the same if the gateway is identified in the configuration file. ssh(1) expands the full name of the gateway and the destination from the configuration file. The following allows the destination host to be reached by entering
ssh server in the terminal.
ProxyCommand ssh jumphost.example.org -W %h:%p
The same can be done for SFTP. Here the destination SFTP server can be reached by entering
sftp sftpserver and the configuration file takes care of the rest. If there is a mix up with the final host key, then it is necessary to add in HostKeyAlias to explicitly name which key will be used to identify the destination system.
ProxyCommand ssh jumphost.example.org -W %h:%p
It is possible to add the key for the gateway to the ssh-agent which you have running or else specify it in the configuration file. The option User refers to the user name on the destination. If the user is the same on both the destination and the originating machine, then it does not need to be used. If the user name is different on the gateway, then the -l option can be used in the ProxyCommand option. Here, the user 'fred' on the local machine, logs into the gateway as 'fred2' and into the destination server as 'fred3'.
ProxyCommand ssh -l fred2 -i /home/fred/.ssh/rsa_key jumphost.example.org -W %h:%p
If both the gateway and destination are using keys, then the option IdentityFile in the config is used to point to the gateway's private key and the option IdentityFile specified on the commandline points at the destination's private key.
ProxyCommand ssh -i /home/fred/.ssh/rsa_key jumphost.example.org -W %h:%p
The old way prior to OpenSSH 5.4 used netcat, nc(1).
ProxyCommand ssh jumphost.example.org nc %h %p
But that should not be used anymore and the netcat mode, provided by -W, should be used instead. The new way does not require netcat at all on any of the machines.
Recursively Chaining Gateways Using stdio Forwarding
The easy way to do this is with ProxyJump which is available starting with OpenSSH 7.3. For older versions, if the route always has the same hosts in the same order, then a straightforward chain can be put in the configuration file. Here three hosts are chained with the destination being given the shortcut machine3.
ProxyCommand ssh -W %h:%p machine1
ProxyCommand ssh -W %h:%p machine2
Thus any machine in the chain can be reached with a single line. For example, the final machine can be reached with
ssh machine3 and worked with as normal. This includes port forwarding and any other capabilities.
Only hostname and, for second and subsequent hosts, ProxyCommand are needed for each Host directive. If keys are not used, then IdentityFile is not needed. If the user is the same for all hosts, the that can be skipped. And if the port is the default, then Port can be skipped. If using many keys in the agent at the same time and the error "too many authentication" pops up on the client end, it might be necessary to add IdentitiesOnly yes to each host's configuration.
Recursively Chaining an Arbitrary Number of Hosts
Again, the easy way to do this is with ProxyJump which is available starting with OpenSSH 7.3. For older versions, it is possible to make the configuration more abstract and allow passing through an arbitrary number of gateways. You can set the user name with -l thanks to the %r@, but that user name will be used for all host that you connect to or through. There are limitations resulting from using the slash as a separator, as there would be with other symbols. However, it allows use of dirname(1) and basename(1) to process the host names.
ProxyCommand ssh %r@$(dirname %h) -W $(basename %h):%p
In this way hosts are separated with a slash (/) and can be arbitrary in number.
$ ssh host1/host2/host3/host4
If keys are to be used then load them into an agent, then the client figures them out automatically if agent forwarding with the -A option is used. However, agent forwarding is not needed if the ProxyJump option (-J) is available. It is considered by many to actually be a security flaw and a general misfeature. So use the ProxyJump option instead if it is available. Also, because the default for MaxAuthTries on the server is 6, using keys normally in an agent will limit the number of keys or hops to 6, with server-side logging getting triggered after half that.
The following configuration uses sed(1) to allow different port numbers and user names using the plus sign (+) as the delimiter for hosts, a colon (:) for ports, and an percentage sign (%) for user names. The basic structure is
ssh -W $() $() and where %h is substituted for the target host name.
ProxyCommand ssh -W $(echo %h | sed 's/^.*+//;s/^\([^:]*$\)/\1:22/') $(echo %h | sed 's/+[^+]*$//;s/\([^+%%]*\)%%\([^+]*\)$/\2 -l \1/;s/:\([^:+]*\)$/ -p \1/')
The port can be left off for the default of 22 or delimited with a colon (:) for non-standard values.
$ ssh host1+host2:2022+host3:2224
As-is, the colons confound sftp(1), so the above configuration will only work with it using standard ports. If sftp(1) is needed on non-standard ports then another delimiter, such as an underscore (_), can be configured.
Any user name except the final one can be specified for a given host using the designated delimiter, in the above it is a percentage sign (%). The destination host's user name is specified with -l and all others can be joined to their corresponding host name with the delimiter.
$ ssh -l user3 user1%host1+user2%host2+host3
If user names are specified, depending on the delimiter, ssh(1) can be unable to match the final host to an IP number and the key fingerprint in known_hosts. In such cases, it will ask for verification each time the connection is established, but this should not be a problem if either the equal sign (=) or percentage sign (%) is used.
ProxyCommand with Netcat
Another way, needed for OpenSSH 5.3 and older, is to use the ProxyCommand configuration directive and netcat. The utility nc(1) is for reading and writing network connections directly. It can be used to pass connections onward to a second machine. In this case, login is the final destination reached via the intermediary jumphost.
$ ssh -o 'ProxyCommand ssh %h nc login.example.edu 22' \
-o 'HostKeyAlias=login.example.edu' \
Keys and different login names can also be used. Using ProxyCommand, ssh(1) will first connect to jumphost and then from there to login.example.edu. The HostKeyAlias directive is needed to look up the right key for login.example.edu, without it the key for jumphost will be tried and that will, of course, fail unless both have the same keys. The account user2 exists on jumphost.
$ ssh -o 'ProxyCommand ssh -i key-rsa -l user2 %h nc login.example.edu 22' \
-o 'HostKeyAlias=login.example.edu' \
It's also possible to make this arrangement more permanent and reduce typing by editing ssh_config Here a connection is made to host2 via host1:
ProxyCommand ssh firstname.lastname@example.org nc %h %p
Here a connection is made to server2 via server1 using the shortcut name 'jump'.
ProxyCommand ssh %h nc server2.example.org 22
It can be made more general:
Host *.example.org my-private-host
ProxyCommand ssh email@example.com nc %h %p
The same can be done with sftp(1) by passing parameters on to ssh(1). Here is a simple example with sftp(1) where machine1 is the jump host to connect to machine2. The user name is the same for both hosts.
$ sftp -o 'ProxyCommand=ssh %h nc machine2.example.edu 22' \
-o 'HostKeyAlias=machine2.example.edu' \
Here is a more complex example using a key for server1 but regular password-based login for the SFTP server.
$ sftp -o 'ProxyCommand ssh -i /Volumes/Home/fred/.ssh/server1_rsa \
-l user2 server1.example.edu nc sftp.example.edu 22' \
-o 'HostKeyAlias=sftp.example.edu' sftp.example.edu
If the user accounts names are different on the two machines, that works, too. Here, 'user2' is the account on the second machine which is the final target. The user 'fred' is the account on the intermediary or jump host.
$ ssh -l user2 \
-o 'ProxyCommand ssh -l fred %h nc machine2.example.org 22' \
-o 'HostKeyAlias machine2.example.org' \
SSH Over Tor
Tunneling SSH Over Tor with Netcat
Instead of using ssh(1) as a SOCKS proxy, it is possible to tunnel the SSH protocol itself over a SOCKS proxy such as Tor. Tor is anonymity software and a corresponding network that uses relay hosts to conceal a user's location and network activity. Its architecture is intended to prevent surveillance and traffic analysis. Tor can be used in cases where it is important to conceal the point of origin of the SSH client.
On the end point that the client sees, Tor is a regular SOCKS5 proxy and can be used like any other SOCKS5 proxy. So this is tunneling SSH over a SOCKS proxy. For example, if Tor is installed locally and listening on a port, then SSH can run over Tor  using netcat:
$ ssh -o ProxyCommand="nc -X 5 -x localhost:9150 %h %p" server.example.org
If the user name on the remote system is different from that on the local system, it is possible to pass along a different user name.
$ ssh -o User=fred -o ProxyCommand="nc -X 5 -x localhost:9150 %h %p" server.example.org
When attempting a connection like this, it is very important that it does not leak information. In particular, the DNS lookup should occur over Tor and not be done by the client itself. Make sure that if VerifyHostKeyDNS is used that it be set to 'no'. The default is 'no' but check to be sure. It can be passed as a run-time argument to remove any doubt or uncertainty.
$ ssh -o "VerifyHostKeyDNS=no" -o ProxyCommand="nc -X 5 -x localhost:9150 %h %p" server.example.org
Using the netcat-openbsd nc(1) package, this seems not to leak any DNS information. Other netcat packages might or might not be the same. It's also not clear if there are other ways in which this method might leak information. YMMV.
Providing SSH as an Onion Service
SSH can be served from an .onion address to provide anonymity and privacy for the client and, to a certain extent, the server. Neither will know where the other is located, though a lot of additional precautions must be taken to come close to anonymizing the server itself and at least some level of trusting the users will still be necessary. Just making it available over Tor is not enough alone to anonymize the server.
However, one of the other advantages of this method is NAT punching. If the SSH service is behind several layers of NAT, then providing it as an Onion service allows passing through those layers seamlessly without configuring each router at each layer. This eliminates the need for a reverse tunnel to an external server and works through an arbitrary number of layers of NAT such as are now found with mobile phone modems.
The first step in making SSH available over Tor is to set up sshd_config(5) so that the SSH service listens on the localhost address and only on the localhost address.
Multiple ListenAddress directives can be used if multiple ports are to be made available. However, any ListenAddress directives provided should bind only to addresses in the 127.0.0.0/8 network or the IPv6 equivalent. Listening to any WAN or LAN address would defeat Tor's anonymity by allowing the SSH server to be identified from its public keys.
The next step in serving SSH over Tor is to set up a Tor client with a hidden service forwarded to the local SSH server. Follow the instructions for installing a Tor client given at the Tor Project web site, but skip the part about the web server if it is not needed. Add the appropriate HiddenServicePort directive to match the address and that sshd(8) is using.
HiddenServicePort 22 127.0.0.1:22
If necessary, add additional directives for additional ports.
Be sure that HiddenServiceDir points to a location appropriate for your system. The onion address of the new SSH service will then be in the file hostname inside the directory indicated by the HiddenServiceDir directive and will be accessible regardless of where it is on the net or how many layers of NAT have it buried. To use the SSH service and verify that it is actually available over Tor, see the preceding section on using the SSH client over Tor with Netcat. If the SSH client settings should be made permanent, then add an entry similar to the following to ~/.ssh/config:
ProxyCommand /usr/bin/nc -x localhost:9050 -X 5 %h %p
Adjust the host, path, and port number as appropriate.
Passing Through a Gateway with an Ad Hoc VPN
Two subnets can be connected over SSH by configuring the network routing on the end points to use the tunnel. The result is a VPN. A drawback is that root access is needed on both hosts, or at least sudo(8) access to ifconfig(8) and route(8). A related more limited and more archaic approach, not presented here but which does not require root at the remote end, would be to use
ssh to establish connectivity and then establish a network using PPP and
Note that there are very few instances where use of a VPN is legitimately called for, not because VPNs are illegal (quite the opposite, indeed data protection laws in many countries make them absolutely compulsory to protect content in transit) but simply because OpenSSH is usually flexible enough to complete most routine sysadmin and operational tasks using normal SSH methods as and when required. This SSH ad-hoc VPN method is therefore needed only very rarely.
Take this example with two networks. One network has the address range 10.0.50.1 through 10.0.50.254. The other has the address range 172.16.99.1 through 172.16.99.254. Each has a machine, 10.0.50.1 and 172.16.99.1 respectively, that will function as a gateway. Local machine numbering starts with 3 because 2 will be used for the tunnel interfaces on each LAN.
+ 10.0.50.2 ===== 172.16.99.2 +
First a tun device is created on each machine, a virtual network device for point-to-point IP tunneling. Then the tun interfaces on these two gateways are then connected by an SSH tunnel. Each tun interface is assigned an IP address.
The tunnel connects machines 10.0.50.1 and 172.16.99.1 to each other, and each are already connected to their own local area network (LAN). Here is a VPN with the client as 10.0.50.0/24, remote as 172.16.99.0/24. First, set on the client:
$ ssh -f -w 0:1 192.0.2.15 true
$ ifconfig tun0 10.1.1.1 10.1.1.2 netmask 255.255.255.252
$ route add 172.16.99.0/24 10.1.1.2
On the server:
$ ifconfig tun1 10.1.1.2 10.1.1.1 netmask 255.255.255.252
$ route add 10.0.50.0/24 10.1.1.1