I have a home server that does various things — file backup, network monitoring, a few small services. It's on a residential internet connection behind a NAT router, so there's no public IP to connect to directly. Configuring port forwarding on the router would work, but it's fragile and the IP changes.
A reverse SSH tunnel solves this cleanly: the home server establishes an outbound SSH connection to a cloud server, and that connection creates a tunnel I can reach from anywhere. The home server only needs outbound internet access — no open inbound ports, no router configuration.
This guide covers setting up the tunnel, making it persistent with autossh, and handling the common issue where the tunnel drops after inactivity.
The technique: the device behind the NAT connects outward to your cloud server (outbound connections are almost never blocked). The cloud server then relays incoming connections back through that tunnel.
I use this on Tencent Cloud Lighthouse as the relay server. The entry-level plan is more than sufficient — a reverse SSH tunnel uses minimal bandwidth and CPU. What matters for this use case is reliability: Lighthouse instances have a static public IP that doesn't change, which is important for the home server or Raspberry Pi that establishes the outbound connection to know where to connect. The server's continuous uptime also means the tunnel can be maintained persistently without worrying about the relay going offline.
- Key Takeaways
Home Device (behind NAT) Cloud Server (public IP)
└─ outbound SSH connection ──→ └─ listens on public port
(connects to cloud) (e.g., 2222)
From anywhere on the internet:
└─ SSH to cloud:2222 ──────────→ tunneled to home device:22
Normal SSH: you connect from your laptop to the server (inbound).
Reverse SSH tunnel: the server connects back to your laptop's location.
The key insight: outbound connections from a device behind NAT work fine. Inbound connections (from the internet directly to the home device) are blocked by the router. A reverse tunnel turns that outbound connection into an access path.
| Requirement | Notes |
|---|---|
| Cloud server | Tencent Cloud Lighthouse with a public IP |
| Local device | Any Linux/macOS/Windows machine you want to access remotely |
| SSH key pair | For passwordless authentication |
By default, SSH only allows forwarded ports to be accessible from localhost. To make the tunnel accessible from the internet, enable GatewayPorts:
ssh ubuntu@YOUR_SERVER_IP
sudo nano /etc/ssh/sshd_config
Add or update:
GatewayPorts yes
sudo systemctl restart sshd
sudo ufw allow 2222/tcp # The port we'll use for remote SSH access
sudo ufw allow ssh # Make sure management SSH stays open
Also open port 2222 in the Lighthouse console firewall tab.
sudo adduser tunneluser --disabled-password --shell /usr/sbin/nologin
A dedicated user with no shell login limits what the tunnel connection can do on the server.
On the device behind NAT (home server, Raspberry Pi, etc.):
ssh-keygen -t ed25519 -C "tunnel-key"
# Save to default location
cat ~/.ssh/id_ed25519.pub
ssh-copy-id ubuntu@YOUR_SERVER_IP
# Or for the tunnel user:
# ssh-copy-id tunneluser@YOUR_SERVER_IP
# This creates a reverse tunnel:
# Port 2222 on the cloud server → port 22 on this local device
ssh -R 2222:localhost:22 ubuntu@YOUR_SERVER_IP -N -f
# -R 2222:localhost:22 = bind port 2222 on the remote server to localhost:22 on the local machine
# -N = don't execute a command
# -f = go to background
Verify the tunnel is working:
# From any machine on the internet, SSH to the cloud server on port 2222
# This should connect you to the local device's SSH
ssh -p 2222 localuser@YOUR_SERVER_IP
If you get a shell on your home device through the cloud server — the tunnel works!
From any internet-connected machine:
# Connect to home device via the cloud server relay
ssh -p 2222 homeuser@YOUR_SERVER_IP
This connects you to homeuser on your home device, tunneled through the cloud server.
Add to ~/.ssh/config on your laptop:
Host home-server
HostName YOUR_SERVER_IP
Port 2222
User homeuser
IdentityFile ~/.ssh/id_ed25519
Then just:
ssh home-server
The simple ssh -R command disconnects if the network drops. autossh monitors the connection and reconnects automatically:
# On the home device:
sudo apt install -y autossh
# Start persistent reverse tunnel
autossh -M 0 \
-o "ServerAliveInterval 30" \
-o "ServerAliveCountMax 3" \
-o "ExitOnForwardFailure yes" \
-N -f \
-R 2222:localhost:22 \
ubuntu@YOUR_SERVER_IP
-M 0 disables autossh's built-in monitoring (uses SSH keepalive instead).
On the local device, create a systemd service that starts the tunnel on boot and restarts it if it fails:
sudo nano /etc/systemd/system/reverse-tunnel.service
[Unit]
Description=Reverse SSH Tunnel to Cloud Server
After=network.target
[Service]
User=YOUR_LOCAL_USER
ExecStart=/usr/bin/autossh -M 0 \
-o "ServerAliveInterval 30" \
-o "ServerAliveCountMax 3" \
-o "ExitOnForwardFailure yes" \
-o "StrictHostKeyChecking no" \
-N \
-i /home/YOUR_LOCAL_USER/.ssh/id_ed25519 \
-R 2222:localhost:22 \
ubuntu@YOUR_SERVER_IP
Restart=always
RestartSec=10s
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable reverse-tunnel
sudo systemctl start reverse-tunnel
sudo systemctl status reverse-tunnel
The tunnel now starts on boot and reconnects automatically if the connection drops.
You can forward multiple ports in a single SSH command:
ssh -R 2222:localhost:22 \ # SSH access
-R 8080:localhost:80 \ # Web server
-R 3306:localhost:3306 \ # MySQL (only accessible from cloud server)
ubuntu@YOUR_SERVER_IP -N
Each -R argument adds another forwarded port.
Forward a web server running on your home device to the cloud server:
-R 8080:localhost:3000
On the cloud server, configure Nginx to proxy yourdomain.com to localhost:8080:
location / {
proxy_pass http://localhost:8080;
}
This makes your home web server accessible at https://yourdomain.com — even though it's behind NAT.
To limit forwarded ports to be accessible only from localhost on the cloud server (not from the internet directly):
In sshd_config, use GatewayPorts clientspecified instead of GatewayPorts yes. Then in the SSH command, bind to localhost explicitly:
-R localhost:2222:localhost:22
This makes port 2222 accessible only from the cloud server itself, not from the internet. You'd then SSH to the cloud server and from there SSH internally to localhost:2222.
Without GatewayPorts yes on the cloud server, reverse-forwarded ports bind to 127.0.0.1 only — accessible from the cloud server itself but not from the internet.
Symptoms: tunnel appears to be running, but ssh -p 2222 ubuntu@YOUR_SERVER_IP times out.
Debug:
# On the cloud server
sudo ss -tlnp | grep 2222
# Should show: 0.0.0.0:2222 (all interfaces) if GatewayPorts is enabled
# Shows: 127.0.0.1:2222 (localhost only) if GatewayPorts is disabled
Fix: add GatewayPorts yes to /etc/ssh/sshd_config on the cloud server and restart sshd.
| Scenario | Setup |
|---|---|
| Access home server SSH | -R 2222:localhost:22 |
| Expose home web server | -R 8080:localhost:80 + Nginx proxy |
| Access home NAS web UI | -R 5001:nas-ip:5001 |
| Remote desktop (VNC) | -R 5900:localhost:5900 |
| Database access | -R 3307:localhost:3306 (MySQL) |
| Local dev to the internet | -R 3000:localhost:3000 + Nginx proxy |
| Issue | Likely Cause | Fix |
|---|---|---|
| Connection refused | Service not running or wrong port | Check systemctl status SERVICE and verify firewall rules |
| Permission denied | Wrong file ownership or permissions | Check file ownership with ls -la and use chown/chmod to fix |
| 502 Bad Gateway | Backend service not running | Restart the backend service; check logs with journalctl -u SERVICE |
| SSL certificate error | Certificate expired or domain mismatch | Run sudo certbot renew and verify domain DNS points to server IP |
| Service not starting | Config error or missing dependency | Check logs with journalctl -u SERVICE -n 50 for specific error |
| Out of disk space | Logs or data accumulation | Run df -h to identify usage; clean logs or attach CBS storage |
| High memory usage | Too many processes or memory leak | Check with htop; consider upgrading instance plan if consistently high |
| Firewall blocking traffic | Port not open in UFW or Lighthouse console | Open port in Lighthouse console firewall AND sudo ufw allow PORT |
How does reverse SSH tunnel differ from a simple SSH tunnel?
SSH tunnels are connection-specific and not persistent without extra tools. reverse SSH tunnel is a dedicated service that runs continuously, supports multiple protocols, and provides a more robust and manageable solution for ongoing tunnel needs.
Is the tunnel traffic encrypted?
Traffic through the Lighthouse server itself can be encrypted depending on the protocol used. For sensitive applications, use TLS/SSL on top of the tunnel for end-to-end encryption.
How do I make the tunnel persistent after server reboots?
Set up both the server and client components as systemd services with Restart=on-failure. They'll start automatically on boot and recover from crashes.
What is the performance overhead of using reverse SSH tunnel?
Minimal for most use cases. Latency adds the round-trip time through the relay server. Throughput is limited by the Lighthouse server's bandwidth and network speed.
Set up your relay server today:
👉 Tencent Cloud Lighthouse — Reliable cloud server for SSH relay
👉 View current pricing and promotions
👉 Explore all active deals and offers