SSH tunnels are a great tool to solve various problems with routing where a firewall does not allow to access a certain local network. They allow forwarding connections to a specified port on the server machine to a local or remote computer through an encrypted channel.
Although there are nice diagrams explaining how SSH tunnels work, they lack detailed description of parameters (e.g. specification of the network interfaces). In this post, I will try to explain the use of a reverse SSH tunnel with full functionality and its parameters. Only TCP ports are considered in this post, but this should work with UNIX sockets as well.
Full functionality: a use case with 4 computers
Reading the SSH manual for the first time may not be that easy to understand:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[johndoe@ArchLinux]% man ssh -R [bind_address:]port:host:hostport -R [bind_address:]port:local_socket -R remote_socket:host:hostport -R remote_socket:local_socket -R [bind_address:]port Specifies that connections to the given TCP port or Unix socket on the remote (server) host are to be forwarded to the local side. This works by allocating a socket to listen to either a TCP port or to a Unix socket on the remote side. Whenever a connection is made to this port or Unix socket, the connection is forwarded over the secure channel, and a connection is made from the local machine to either an explicit destination specified by host port hostport, or local_socket, or, if no explicit destination was specified, ssh will act as a SOCKS 4/5 proxy and forward connections to the destinations requested by the remote SOCKS client. Port forwardings can also be specified in the configuration file. Privileged ports can be forwarded only when logging in as root on the remote machine. IPv6 addresses can be specified by en‐ closing the address in square brackets. By default, TCP listening sockets on the server will be bound to the loopback interface only. This may be overridden by specifying a bind_address. An empty bind_address, or the address ‘*’, indicates that the remote socket should listen on all interfaces. Specifying a remote bind_address will only succeed if the server's GatewayPorts option is enabled (see sshd_config(5)). If the port argument is ‘0’, the listen port will be dynamically allocated on the server and reported to the client at run time. When used together with -O forward the allocated port will be printed to the standard output. |
Nevertheless, plain English can be translated to a 2-dimensional language of drawings. Here is the diagram depicting the process of establishing a reverse SSH tunnel between "server" computer and "host" routed through "client", where command "ssh" is executed:

A use case can be demonstrated with 4 computers, 3 of which reside in network 10.0.0.* and 2 computers reside in network 192.168.0.*:

An HTTP request sent by command "curl" on the orange computer on the right will return an HTML page with a list of files in the directory where "python3 -m http.server 80" is being executed on the blue computer on the left.
This works only when "GatewayPorts" parameter of the SSHD daemon is set to "clientspecified", which allows "client" to choose a network interface to bind to:
1 2 3 4 5 6 |
[johndoe@ArchLinux]% man sshd_config GatewayPorts Specifies whether remote hosts are allowed to connect to ports forwarded for the client. By default, sshd(8) binds remote port forwardings to the loopback address. This prevents other remote hosts from connecting to forwarded ports. GatewayPorts can be used to specify that sshd should allow remote port forwardings to bind to non-loopback addresses, thus allowing other hosts to connect. The argument may be no to force remote port forwardings to be available to the local host only, yes to force remote port forwardings to bind to the wildcard address, or clientspecified to allow the client to select the address to which the forwarding is bound. The default is no. |
Binding to all network interfaces on the server
When "GatewayPorts" parameter of the SSHD daemon is set to "clientspecified", leaving empty "bind_addr" (there is a colon ":" before "port" argument!) will bind to all network interfaces (Diagram 1).
However, this freedom of choice of network interfaces is not always the case and the binding behavior of the tunnel depends on the "GatewayPorts" setting.
When "GatewayPorts" parameter of the SSHD daemon is set to "yes", parameter "bind_addr" has no effect at all, the server will bind to all network interfaces regardless of the value of "bind_addr", so it is logical to create a tunnel by omitting "bind_addr" parameter:

Similarly, when "GatewayPorts" parameter of the SSHD daemon is set to "no", argument "bind_addr" has no effect at all, the server will bind to loopback interface with IPv4 address 127.0.0.1 or IPv6 address [::1] regardless of the value of "bind_addr". So it is better to omit "bind_addr" parameter completely. If you still need to bind to all network interfaces, then a trick with socket forwarding might help:

By default "socat" binds "glbport" to all network interfaces, but this option can be specified explicitly as well:
1 |
ssh -R "2001:localhost:8000" user@server "socat TCP-LISTEN:2002,fork,bind=0.0.0.0 TCP:localhost:2001" |
Special case: endpoint of a tunnel is client itself
Usually, one end of a tunnel is terminated at the client itself. This special case can be described using the following diagram ("GatewayPorts" is set to "yes"):

Depending on the value of "GatewayPorts" setting, the binding behavior may change:

When unset, the default value of "GatewayPorts" parameter is "no", so Diagram 6 shows the most frequent scenario which may occur in daily life. The server will allow incoming connections to its "port" only from the localhost (itself).
Tips and Tricks
Use "tmux" to enable multiple terminals in a single screen.
Choose ports in the range 1025-65535, ports below 1025 require root permissions to be used.
Use identity file (private key) to log into the server without entering password: "-i ~/.ssh/id_rsa".
Option "-f" requests "ssh" to go to background just before command execution. Useful for automating tunnel creation in scripts.
Use flag "-N" for disabling execution of a remote command. This is useful for just forwarding ports, e.g.
1 |
ssh -N -f -i ~/.ssh/id_rsa -R "2001:localhost:8000" user@server |
but if there is a command to execute, then this flag should be omitted:
1 |
ssh -f -i ~/.ssh/id_rsa -R "2001:localhost:8000" user@server "socat TCP-LISTEN:2002,fork TCP:localhost:2001" |
Debugging on the client side can be done by increasing verbosity ("-v" flag):
1 |
ssh -vvv -N -f -i ~/.ssh/id_rsa -R "2001:localhost:8000" user@server |
Debugging on the server side can be done by specifying the debug mode "-d":
1 |
/usr/bin/sshd -ddd -f /etc/ssh/sshd_config |
If ssh client hangs without throwing any errors, try to unset SSH_AUTH_SOCK variable and retry the attempt:
1 2 |
unset SSH_AUTH_SOCK ssh -vvv -N -f -i ~/.ssh/id_rsa -R "2001:localhost:8000" user@server |
Listing the status of a specified port (e.g. port 80) can be done by grepping for it in the "netstat" output:
1 2 3 4 |
[johndoe@ArchLinux]% netstat -tlpn 2>/dev/null | sed --quiet "1,2p;/80/p" Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp6 0 0 :::80 :::* LISTEN - |
References:
"ssh - OpenSSH remote login client", manual page
"sshd_config - OpenSSH daemon configuration file", manual page
"SSH Tunneling Explained" by Buddhika Chamith
"SSH port forwarding visualized" by Dirk Loss
"How does reverse SSH tunneling work?", StackOverflow