Forward a TCP port from an SSH client to an SSH server
Content |
Tested on |
Debian (Etch, Lenny, Squeeze) |
Ubuntu (Hardy, Intrepid, Jaunty, Karmic, Lucid, Maverick, Natty, Precise) |
Objective
To forward connections on a particular TCP port from an SSH client to an SSH server.
Scenario
Suppose that an organisation has two networks: a private, internal network (192.168.0.0/24) and a DMZ (203.0.113.0/24). Machines on the internal network are accessible from the DMZ, but not from the public Internet. Machines in the DMZ are accessible from anywhere.
There is an webserver www.private.example.com
(192.168.0.1) located on the internal network. You wish to access this machine from the public Internet (198.51.100.1). You cannot connect to it directly, but you can connect using SSH to the machine ssh.example.com
(203.0.113.1) which is located in the DMZ.

Method
Overview
When you connect to a machine using SSH you can ask for one or more TCP ports to be forwarded through the secure channel. This does not interfere with the normal operation of SSH, and if required you can multiplex several ports through the same channel. The endpoints of a forwarded connection need not be on the machines running SSH.
In addition to forwarding the port, it may be beneficial to add an entry to /etc/hosts
so that the destination can be referred to by name. Some protocols (such as HTTP with name-based virtual hosting) may be unusable without this.
Some protocols will not work at all when forwarded, with or without an /etc/hosts
entry. The issues are similar to those which arise when performing NAT, so if a protocol cannot traverse a NAT gateway (or can do so only with explicit support from the gateway) then it is unlikely to work with port forwarding.
Establish the SSH connection
To forward a port from the client to the server use the -L
option when establishing the connection:
ssh -L 127.0.0.1:8000:192.168.0.1:80 ssh.example.com
The four colon-separated arguments following the -L
option have the following meaning:
- The first argument (127.0.0.1) is the address that the SSH client should bind to when listening for connections.
- The second argument (8000) is the port number on which the SSH client should listen for connections. If this is a privileged port (below 1024) then the client will need permission to bind to it (see below).
- The third argument (192.168.0.1) is the address to which the SSH server should forward connections. This has been specified numerically because if you add an entry to
/etc/hosts
(as described below) the hostname would not resolve to the appropriate IP address. - The fourth argument (80) is the port number to which the SSH server should forward connections. It does not matter whether this port is privileged or not.
Binding to the loopback address prevents other machines from connecting to the port, which is almost certainly the right behaviour for the scenario described here. If you want to allow connections from other machines then leave this field empty:
ssh -L :8000:192.168.0.1:80 ssh.example.com
If you want the SSH client to bind to a privileged port then the simplest (but least secure) method is to run it as root. Alternatives include using authbind
, setting the cap_net_bind_service
capability, or redirecting the port before it reaches SSH using iptables.
Optionally, add the final destination to /etc/hosts
Because www.private.example.com
is not accessible from the public Internet there is a good chance that it will not be listed in the public DNS. Even if it is, the address will be wrong for connecting through SSH (it will be listed as 192.168.0.1).
The ability to refer to the server by name could be more than just a matter of convenience. For example, HTTP version 1.1 allows a webserver to host many websites on a single IP address using a technique called name-based virtual hosting. This does not work if you use an IP address in the URL: you must use a hostname in order to identify the website to be served.
The simplest solution is to temporarily add the hostname to /etc/hosts
:
127.0.0.1 www.private.example.com
Note that the name has been mapped to the loopback address, not the public address of the client (198.51.100.1). This allows the SSH client to be bound to the loopback address in order to prevent connections from other machines (see above).
Remember to remove this entry from /etc/hosts
once you have finished using it, especially if the client machine is ever likely to be connected to the internal network.
(One way to avoid the issue described above is to connect to a proxy server on the internal network instead of the webserver directly. This has the additional benefit of allowing access to any webserver on the internal network through a single port, but the disadvantage of forwarding all of your web traffic indiscriminately regardless of whether it is destined for the internal network or the public Internet.)
Testing
If you have added the webserver hostname to /etc/hosts
then you should be able to fetch the home page of the website from the URL http://www.private.example.com:8000/
using a web browser running on 198.51.100.1.
Otherwise, you will need to fetch it from http://127.0.0.1:8000/
. As noted above, this will probably not give the intended result if the webserver uses name-based virtual hosting.
Troubleshooting
See Troubleshooting SSH port forwarding.
Variations
Enable automatic port forwarding for all SSH connections
If you want every SSH connection to forward a particular port automatically then this can be arranged using the LocalForward
configuration option. For example, the equivalent to
-L 127.0.0.1:8000:192.168.0.1:80
on the command line would be:
LocalForward 127.0.0.1:8000 192.168.0.1:80
This line can be placed either in the configuration file for a particular user (typically ~/.ssh/config
) or the global configuration file (typically /etc/ssh/ssh_config
). Note the space between the first port and the second IP address: on the command line this was a colon.
See also
Tags: ssh