Reach an SSH server behind NAT without opening an inbound firewall port. The target machine makes an outbound SSH connection to Serveo; your client uses Serveo as a jump host to reach it.
Start the reverse tunnel on the target
Run this on the machine you want to reach. Replace my-long-private-alias with a long, hard-to-guess name and keep the command running:
ssh -NT -o ExitOnForwardFailure=yes -R my-long-private-alias:22:localhost:22 serveo.netThis creates a private Serveo route. It does not open port 22 directly to the public Internet, but the alias is a routing name rather than an authentication secret. Keep public-key authentication enabled on the target SSH server.
Connect from your client
On the computer you are connecting from, use Serveo as the OpenSSH jump host:
ssh -J serveo.net your-user@my-long-private-aliasThe first SSH connection terminates at Serveo. Serveo then carries the second SSH connection to port 22 on the target through the existing reverse tunnel. Your target server still authenticates your-user normally.
Give the client a short name
Add this to ~/.ssh/config on the client:
Host home-via-serveo
HostName my-long-private-alias
User your-user
ProxyJump serveo.net
ServerAliveInterval 30
ServerAliveCountMax 3Then connect with:
ssh home-via-serveoKeep the target tunnel running on Linux
Before automating it, configure an SSH key that works non-interactively and run the target command by hand. Then create a user service at ~/.config/systemd/user/serveo-tunnel.service:
[Unit]
Description=Serveo reverse SSH tunnel
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/bin/ssh -NT -o BatchMode=yes -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -R my-long-private-alias:22:localhost:22 serveo.net
Restart=always
RestartSec=10
[Install]
WantedBy=default.targetsystemctl --user daemon-reload
systemctl --user enable --now serveo-tunnel.service
journalctl --user -u serveo-tunnel.service -fAn abrupt network failure can leave an alias occupied briefly while the old connection is detected and removed. The keepalives above make the client reconnect promptly; if Serveo reports that the alias is already taken, let the service retry instead of starting competing copies.
Windows
Current Windows releases include an OpenSSH client, so the same target and client commands work in PowerShell. Start by following the Windows OpenSSH and key setup section. Once the command works interactively, Windows Task Scheduler can start ssh.exe at login. Keep the SSH options as separate arguments and select a restart-on-failure policy.
Troubleshooting
- Alias already taken: another live or recently disconnected tunnel owns that alias and port. Stop duplicate clients and retry after the stale connection clears.
- Permission denied on the second connection: the target SSH server rejected your user or key. Test
ssh your-user@localhoston the target first. - Forwarding request failed: keep
ExitOnForwardFailure=yesenabled so automation exits and retries instead of appearing healthy without a tunnel. - Port 22 is blocked: the target can connect to Serveo over port 443 with
ssh -p 443 ... serveo.net. - Old OpenSSH client: use the ProxyCommand form in the documentation when
-Jis unavailable.
Security boundary
Serveo transports the connection; it does not replace SSH authentication on your target. Disable password login where practical, protect target keys, choose an unguessable alias, and stop the reverse tunnel when it is no longer needed. For long-lived network access across changing Wi-Fi connections, also consider Serveo's WireGuard transport.