In tunneling, or port forwarding, a local port is connected to a port on a remote host or vice versa. So connections to the port on one machine are really connections to a port on the other machine.
The ssh(1) options -f (go to background), -N (do not execute a remote program) and -T (disable pseudo-tty allocation) can be useful for connections that are used only for creation of tunnels.
In regular port forwarding, connections to a local port are forwarded to a port on a remote machine. This is a way of securing an insecure protocol or of making a remote service appear as local. Here we forwarded VNC in two steps. First make the tunnel:
$ ssh -L 5901:localhost:5901 -l fred desktop.example.org
Then on the local machine, connections to the forwarded port will really be connecting to the remote machine.
Multiple tunnels can be specified at the same time. The tunnels can be of any kind, not just regular forwarding. See the next section below for reverse tunnels. For dynamic forwarding see the section Proxies and Jump Hosts.
$ ssh -L 5901:localhost:5901 \ -L 5432:localhost:5432 \ -l fred desktop.example.org
If a connection is only used to create a tunnel, then it can be told not to execute any remote programs (-N), making it a non-interactive session, and to drop to the background (-f).
$ ssh -fN -L 3128:localhost:3128 -l fred server.example.org
Note that -N will work even if the authorized_keys forces a program using the command= option. So a connection using -N will stay open instead of running a program and then exiting.
Tunneling via an intermediate host
Tunneling can go via one intermediate host to reach a second host, the latter does not need to be on a publicly accessible network. However, the target port on the second remote machine does have to be accessible on the same network as the first. Here, 192.168.0.101 and bastion.example.org must be on the same network and, in addition, bastion.example.org has to be directly accessible to the client machine running ssh(1). So, port 80 on 192.168.0.101 has to be available to the machine bastion.example.org.
$ ssh -fN -L 1880:192.168.0.101:80 -l fred bastion.example.org
Thus, to connect to port 80 on 192.168.2.101 via the host bastion.example.org, once the tunnel is made, connect to port 1880 on localhost. This way works for one or two hosts. It is also possible to chain multiple hosts, using a different method.
For more about passing through intermediate computers, see the Cookbook section on Proxies and Jump Hosts.
A reverse tunnel goes the opposite direction of a regular tunnel. In a reverse tunnel, a port on the remote host is forwarded to the local host. Once the connection is made, it works the same as a regular tunnel. Connections to the destination port on the local host connect to the remote host's port.
On the machine that will become the remote machine, open a reverse tunnel.
$ ssh -fNT -R 2022:localhost:22 -l fred server.example.org
Then on the local machine, connecting to the forwarded port as the local host, will open the connection to the machine hosting the reverse tunnel.
$ ssh -p 2022 -l fred localhost
A common use-case for reverse tunneling is when you have to access a machine that is behind a firewall that blocks incoming ssh but without changing the firewall settings, and you have direct access to a second machine outside the firewall. It is easy to make a reverse tunnel from the machine behind the firewall to the second machine. Then to conect to the first machine from outside, connect to the forwarded port on the second machine. The second machine on the outside acts as a relay server to forward connections to the machine on the inside.
Adding or Removing Tunnels within an Established Connection
It is possible to add or remove tunnels, reverse tunnels, and SOCKS proxies to or from an existing connection using an escape sequence. The default escape character is the tilde (~) and the full range of options is described in the manual page for ssh(1). Escape sequences only work if they are the first characters entered on a line and if followed by a return. When adding or removing a tunnel to or from an existing connection, ~C, the command line is used.
To add a tunnel in an active SSH session, use the escape sequence to open a command line in SSH and then enter the parameters for the tunnel:
~C L 2022:localhost:22
To remove a tunnel from an active SSH session is almost the same. Instead of -L, -R, or -D we have -KL, -KR, and -KD plus the port number. Use the escape sequence to open a command line in SSH and then enter the parameters for removing the tunnel.
Adding or Removing Tunnels within a Multiplexed Connection
There is an additional option for forwarding when multiplexing. More than one SSH connection can be multiplexed over a single TCP connection. Control commands can be passed to the master process to add or drop port forwarding to the master process.
First a master connection is made and a socket path assigned.
$ ssh -S '/home/fred/.ssh/%h:%p' -M server.example.org
Then using the socket path, it is possible to add port forwarding.
$ ssh -O forward -L 2022:localhost:22 -S '/home/fred/.ssh/%h:%p' email@example.com
Since OpenSSH 6.0 it is possible to cancel specific port forwarding using a control command.
$ ssh -S "/home/fred/.ssh/%h:%p" -O cancel -L 2022:localhost:22 firstname.lastname@example.org
For more about multiplexing, see the Cookbook section on Multiplexing.
Restricting Tunnels to Specific Ports
By default, port forwarding will allow forwarding to any port if it is allowed at all. The way to restrict which ports a user can use in forwarding is to apply the PermitOpen option on the server side either in the server's configuration or inside the user's public key in authorized_keys. For example, with this setting in sshd_config(5) all users can forward only to port 7654 on the server, if they try forwarding:
Multiple ports may be specified on the same line if separated by whitespace.
PermitOpen localhost:7654 localhost:3306
If the client tries to forward to a disallowed port, there will be a warning message that includes the text "open failed: administratively prohibited: open failed" while the connection otherwise continues as normal. However, even if the client has ExitOnForwardFailure in its configuration a connection will still succeed, despite the warning message.
However, if shell access is available, it is possibile to run other port forwarders, so without further restrictions, PermitOpen is more of a reminder or recommendation than a restriction. But for many cases, such a reminder might be enough.
The PermitOpen option can be used as part of one or more Match blocks if forwarding options need to vary depending on various combinations of user, group, client address or network, server address or network, and/or the listening port used by sshd(8). If using the Match criteria to selectively apply rules for port forwarding, it is also possible to prevent the account from getting an interactive shell by setting PermitTTY to no. That will prevent the allocation of a pseudo-terminal (PTY) on the server thus prevent shell access, but allow other programs to still be run unless an appropriate forced command is specified in addition.
Match Group mariadbusers PermitOpen localhost:3306 PermitTTY no ForceCommand /usr/bin/true
With that stanza in sshd_config(5) it is only possible to connect by adding the -N option to avoid executing a remote command.
$ ssh -N -L 3306:localhost:3306 server.example.org
The -N option can be used alone or with the -f option which drops the client to the background once the connection is established.
Without the ForceCommand option in a Match block in the server configuration, if an attempt is made to get a PTY by a client that is blocked from getting one, the warning "PTY allocation request failed on channel n" will show up on the client, with n being the channel number, but otherwise the connection will succeed without a remote shell and the port(s) will still be forwarded. Various programs, including shells, can still be specified by the client, they just won't get a PTY. So to really prevent access to the system other than forwarding, a forced command is needed. The tool true(1) comes in handy for that. Note that true(1) might be in a different location on different systems.
Limiting Port Forwarding Requests Using Keys Only
The following authorized_keys line shows the PermitOpen option prepended to a key in order to limit a user connecting with that particular key to forwarding to just port 8765:
permitopen="localhost:8765" ssh-ed25519 AAAAC3NzaC1lZDI1NT...
Multiple PermitOpen options may be applied to the same public key if they are separated by commas and thus a key can allow multiple ports.
By default, shell access is allowed. With shell access it is still possible to run other port forwarders. The no-pty option inside the key can facilitate making a key that only allows forwarding and not an interactive shell, if combined with an appropriate forced command using the command option.
no-pty,permitopen="localhost:9876",command="/usr/bin/true ssh-ed25519 AAAAC3NzaC1lZDI1NT...
The no-pty option blocks interactive shell. The client will still connect to the remote server and will allow forwarding but will respond with an error including the message "PTY allocation request failed on channel n". But as mentioned in the previous subsection, there are still a lot of ways around that and adding the command option hinders them.
This method is awkward to lock down. If the account has write access in any way, directly or indirectly, to the authorized_keys file, then it is possible for the user to add a new key or overwrite the existing key(s) with more permissive options. In order to prevent that, the server has to be configured to look for the file in a location outside of the user's reach. See the section on Public Key Authentication for details on that. The methods described above in the previous subsection using sshd_config(5) might be more practical in many cases.