SSH Reverse Tunnels to Expose a Local Service
Publish a laptop or lab port through a Lucknow VPS with ssh -R, bind it on localhost, and keep the tunnel alive without opening the lab to the internet.
Netbay Developer Relations
Netbay Engineering
On this page
A reverse tunnel takes a port on the VPS and forwards it to a port on the machine that opened the SSH session. You sit on a laptop behind NAT, run a development server on 8000, and ssh -R 127.0.0.1:8080:127.0.0.1:8000 vps. A process on the VPS that connects to 127.0.0.1:8080 is talking to your laptop. The lab never gets a public address. The VPS is the only public TCP listener, and even that listener can stay on localhost if the consumer is on the VPS itself.
This is not a VPN and it is not a reason to run GatewayPorts yes on a hunch. It is a precise hole, with a precise bind address, that you can explain.
The flag, in an order you can remember
ssh -R remote_bind:remote_port:local_host:local_port user@vps. Read it from the VPS outward. The first pair is where sshd listens. The second pair is where the client (your laptop) connects onward. 127.0.0.1:8080:127.0.0.1:8000 means sshd binds 8080 on the VPS loopback and, for each connection, the laptop connects to its own 8000.
ssh -N -R 127.0.0.1:8080:127.0.0.1:8000 deploy@vps
ssh -N -R 127.0.0.1:5432:127.0.0.1:5432 deploy@vps-N means no remote shell, which is what you want for a tunnel-only session. Bind 127.0.0.1 on the remote side unless you have a documented reason not to. If you omit the bind address, OpenSSH uses GatewayPorts to decide; the default is localhost. That default is correct.
A reverse tunnel of 5432 is how you let a reporting job on the VPS query a database that must never listen on the public internet. The database still authenticates. The tunnel is not an authorization layer. It is a path.
GatewayPorts, and why the default is right
sshd directive GatewayPorts no (the default) forces remote-forward listeners onto loopback even if you asked for 0.0.0.0. GatewayPorts yes honors the bind address you passed. GatewayPorts clientspecified lets the client choose. Turning yes on a public Lucknow VPS means anyone who can reach that port can reach whatever you forwarded, subject only to whatever auth the local service has. A development server with no password becomes a public development server.
If you need a colleague to hit the forwarded port, do not open GatewayPorts. Have them SSH to the VPS and use the loopback, or add a local forward on their side to 127.0.0.1:8080 on the VPS. Two SSH hops, still no public bind. If you truly must bind publicly, put a reverse proxy with TLS and auth in front, and still prefer a firewall allowlist. L3/L4 DDoS filtering on the VPS does not make an unauthenticated app safe.
AllowTcpForwarding remote (or yes) must be on for -R to work. You can set AllowTcpForwarding local on app nodes that should never accept reverse tunnels, and keep yes only on the box you use as the publisher.
Keep the tunnel up without a terminal
A laptop lid close kills the session. For a lab that needs to stay published during a demo, run the client under a supervisor on the laptop, not as a forgotten screen session.
Host publish-lab
HostName vps.example
User deploy
IdentityFile ~/.ssh/id_ed25519_prod
IdentitiesOnly yes
RemoteForward 127.0.0.1:8080 127.0.0.1:8000
ExitOnForwardFailure yes
ServerAliveInterval 30
ServerAliveCountMax 3
SessionType nonessh -f -N publish-lab backgrounds the client after the forward is up. ExitOnForwardFailure yes is mandatory: if 8080 is taken on the VPS, you want a crash, not a silent shell with no tunnel. A systemd --user unit with Restart=always is the grown-up form. On the VPS, a matching systemd unit is the wrong place; the client must originate the reverse tunnel because the lab is behind NAT.
If two publishers fight for the same remote port, the second fails. Pick ports on purpose. Document them next to the Host alias.
What not to reverse-tunnel
Do not reverse-tunnel the Docker socket, sshd itself, or a database with a default password. Do not use a reverse tunnel as a poor man's ingress for production traffic; that is what a real service on the VPS is for. Do not stack reverse tunnels through a jump host unless you can draw the bind addresses on paper. Nested -R is legal and easy to misread.
A short takeaway: -R publishes a local port onto the VPS, bind it on 127.0.0.1, keep GatewayPorts off, and supervise the client. The lab stays private; the Lucknow VPS is the only public TCP endpoint, and even that can stay loopback. Bring up an Ubuntu 24.04 box on Netbay in under 60 seconds and try the loopback form first — netbayhosts.in.
Keep reading
Follow along on a real VPS
Deploy Linux in under 60 seconds
These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.
Deploy an instance