How it works
A machine behind NAT cannot accept incoming connections, but it can make outgoing ones. So it connects out to a server with a public address, and asks that server to listen on a port. Connections to that port travel back through the existing SSH connection.
For the other two kinds of forward, see local, remote and dynamic SSH tunnels.
SSH into the hidden machine
The hidden machine needs its own SSH server running. On the hidden machine:
ssh -N -o ExitOnForwardFailure=yes -R 2222:localhost:22 user@server
-N runs no remote command. -R 2222:localhost:22 means port 2222 on the server leads to port 22 on this machine. Add -f to put it in the background.
On the server, you can see the port and use it:
$ ss -ltn | grep 2222
LISTEN 0 128 127.0.0.1:2222 0.0.0.0:*
LISTEN 0 128 [::1]:2222 [::]:*
$ ssh -p 2222 hiddenuser@localhost
From your laptop, go through the server in one step:
ssh -J user@server -p 2222 hiddenuser@localhost
To avoid mixing up host keys, since localhost could mean many machines, put this in ~/.ssh/config:
Host home
HostName localhost
Port 2222
User hiddenuser
ProxyJump user@server
HostKeyAlias home
Show a local web app
ssh -N -R 8080:localhost:3000 user@server
Now curl http://localhost:8080/ on the server reaches port 3000 on your machine. In our test, from a different machine, curl http://server:8080/ was refused until we changed GatewayPorts.
With port 0, the server picks a free port and ssh prints it:
$ ssh -N -R 0:localhost:3000 user@server
Allocated port 33341 for remote forward to localhost:3000
GatewayPorts: who can reach the port
This setting is in the server's sshd_config and defaults to no. What we measured:
| GatewayPorts | -R 8080:... | -R 0.0.0.0:8080:... |
|---|---|---|
no | Loopback only | Loopback only, and the address is ignored |
clientspecified | Loopback only | All addresses, reachable from other machines |
yes | All addresses | All addresses |
With yes, even -R localhost:8080:... listened on all addresses in our test. If you need outside access, clientspecified gives the client the choice, and the firewall still has to allow the port.
Keep it private
- Leave
GatewayPorts noand use-Jas shown above. Then only someone who can log in to the server can reach the port. - Remember the other users on the server. Loopback is shared by everyone logged in there. They can reach the port, and the service behind it still needs its own login.
- Use a dedicated key and account for the tunnel, since the machine behind NAT keeps a key that can log in to your server. The autossh guide shows a tested restricted setup.
When the port is taken
With ExitOnForwardFailure=yes, ssh stops and exits with code 255:
Error: remote port forwarding failed for listen port 2222
Without it, you only get a Warning and ssh stays connected with no tunnel, which is easy to miss. A common cause is that an old connection still holds the port on the server. AllowTcpForwarding no or a PermitListen rule on the server gives the same kind of failure.
Keep it running
A plain ssh -R ends when the connection drops, and nothing restarts it. For a permanent tunnel, follow autossh and systemd.
Questions
What is the difference between ssh -L and ssh -R?
-L opens a port on your machine that leads to something the server can reach. -R does the opposite: it opens a port on the server that leads back to your machine.
Why can't other machines reach my reverse tunnel port?
With the default GatewayPorts no, the server listens only on its loopback. Set GatewayPorts clientspecified or yes in sshd_config, and allow the port in the firewall.
How do I keep a reverse SSH tunnel running?
A plain ssh -R ends when the connection drops. Run it under autossh and systemd so it restarts by itself.