Reverse SSH Tunnels: Access Your Home Computer From Anywhere
# Reverse SSH Tunnels: Access Your Home Computer From Anywhere
My phone buzzes at 2 AM. A critical server config file is on my home desktop, behind NAT, behind my ISP's double-NAT, unreachable. That was the last time I got locked out of my own machine.
## The NAT Problem
Your home computer doesn't have a public IP. It's hidden behind your router's NAT, invisible to the internet. Port forwarding works until your ISP decides you're behind carrier-grade NAT too. Dynamic DNS helps, but not when there's no port to forward.
"The solution isn't to punch holes inward. It's to reach outward from where you already are."
## The Architecture
┌─────────────────────────────────────────────────────────────────┐
│ INTERNET │
└─────────────────────────────────────────────────────────────────┘
│ │
│ (1) Outbound SSH │ (3) Connect to
│ Connection │ VPS:7000
│ │
▼ │
┌─────────────────┐ ┌──────┴──────┐
│ HOME COMPUTER │◄───── Tunnel ─────►│ VPS │◄──── PHONE
│ (Behind NAT) │ │ (Public IP) │
│ │ │ │
│ SSH Server │ (2) VPS opens │ Port 7000 │
│ Port 22 │ port 7000 │ listening │
└─────────────────┘ └─────────────┘The magic is in the direction. Your home computer initiates an outbound connection to a VPS you control. That connection carries a reverse tunnel that exposes your home's SSH port on the VPS. Now your phone connects to the VPS, and traffic flows backward through the tunnel.
## Key Components
| Component | Purpose |
|---|---|
| VPS | Public IP endpoint, tunnel bridge |
| autossh | Maintains persistent tunnel with auto-reconnect |
| GatewayPorts | SSH config allowing remote port binding |
| fail2ban | Brute-force protection on exposed port |
| systemd | Service management with hardening |
## The SSH Command Anatomy
The core of the system is a single SSH command:
ssh -N -R 0.0.0.0:7000:localhost:22 user@vpsBreaking it down:
- >
-N: No remote command, just forward ports - >
-R: Reverse tunnel (remote to local) - >
0.0.0.0:7000: Bind to all interfaces on port 7000 (on the VPS) - >
localhost:22: Forward to local SSH server - >
user@vps: Your VPS credentials
0.0.0.0 is critical. Without it, the tunnel binds only to 127.0.0.1 on the VPS, unreachable from outside.
## GatewayPorts: The Server-Side Gate
SSH servers block remote port binding by default. You need to explicitly enable it:
# In /etc/ssh/sshdconfig
GatewayPorts clientspecifiedTwo modes exist:
| Mode | Behavior |
|---|---|
clientspecified | Client must explicitly request 0.0.0.0 binding |
yes | All remote forwards bind to 0.0.0.0 automatically |
clientspecified. It requires explicit intent, reducing accidental exposure.
## Persistence with autossh
Raw SSH dies on network hiccups. autossh wraps SSH with monitoring and auto-restart:
autossh -M 0 \
-o "ServerAliveInterval=30" \
-o "ServerAliveCountMax=3" \
-o "ExitOnForwardFailure=yes" \
-N -R 0.0.0.0:7000:localhost:22 \
-i ~/.ssh/idrsatunnel \
user@vpsKey options:
- >
-M 0: Disable autossh's monitoring port, use SSH keepalives instead - >
ServerAliveInterval=30: Send keepalive every 30 seconds - >
ServerAliveCountMax=3: Disconnect after 3 missed responses (90 seconds) - >
ExitOnForwardFailure=yes: Fail fast if tunnel can't be established
## Systemd Service with Hardening
Wrapping autossh in systemd gives us boot persistence and security:
[Unit]
Description=Persistent Reverse SSH Tunnel
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=youruser
Environment="AUTOSSHGATETIME=0"
ExecStart=/usr/bin/autossh -M 0 -o ServerAliveInterval=30 ...
Restart=always
RestartSec=10
# Security hardening
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
The hardening options:
- >
NoNewPrivileges: Process can't gain additional privileges - >
ProtectSystem=strict: Filesystem is read-only except explicit paths - >
ProtectHome=read-only: Home directory read-only (SSH keys readable) - >
PrivateTmp: Isolated /tmp directory
## Fail2ban: Brute-Force Protection
An open port on the internet attracts scanners within minutes. Fail2ban bans IPs after failed attempts:
[reverse-ssh-tunnel]
enabled = true
port = 7000
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600
findtime = 600This configuration:
- >Monitors auth.log for failed SSH attempts on port 7000
- >Bans IPs for 1 hour after 5 failures within 10 minutes
## The Setup Scripts
I automated the entire process into two scripts:
### Server Script (VPS)
sudo ./setup-server.shHandles:
- Backup existing sshdconfig
- Configure GatewayPorts
- Open firewall port
- Install and configure fail2ban
- Restart SSH daemon
### Client Script (Home Computer)
./setup-client.shHandles:
- Install autossh
- Ensure local SSH server running
- Generate dedicated tunnel SSH key
- Copy key to VPS
- Create hardened systemd service
- Enable and start tunnel
## Security Considerations
### Dedicated Tunnel Key
Never reuse keys. Generate a dedicated key for the tunnel:
ssh-keygen -t ed25519 -f ~/.ssh/idrsatunnel -N "" -C "reverse-tunnel"If compromised, revoke only this key.
### Disable Password Authentication
Once key-based auth works, disable passwords on your home machine:
# /etc/ssh/sshdconfig
PasswordAuthentication no
PubkeyAuthentication yes### Non-Standard Port
Default port 7000 works, but a random high port (10000-65000) reduces scanner noise:
# Less noise, same security
ssh -R 0.0.0.0:47832:localhost:22 user@vps### Phone SSH Key
Generate a key on your phone's SSH app and add it to ~/.ssh/authorized_keys on your home computer. Never authenticate with passwords over the tunnel.
## Troubleshooting
### Tunnel Not Binding to 0.0.0.0
# On VPS, check what's listening
ss -tlnp | grep 7000
# Should show: 0.0.0.0:7000
# Not: 127.0.0.1:7000
Fix: Ensure GatewayPorts clientspecified is set and client uses 0.0.0.0:7000 in the -R flag.
### Connection Drops
Increase keepalive tolerance:
-o "ServerAliveInterval=60"
-o "ServerAliveCountMax=5"### Service Won't Start
Check logs:
sudo journalctl -u ssh-tunnel -n 50Common issues:
- >Key permissions (should be 600)
- >VPS unreachable
- >Port already in use
## Why Not VPN?
VPNs work, but they're heavyweight. A reverse SSH tunnel:
- >Uses existing SSH infrastructure
- >No additional software on the VPS
- >Works through restrictive firewalls that block VPN protocols
- >Minimal resource usage
- >Simple to debug
## Conclusion
Reverse SSH tunnels invert the traditional connection model. Instead of fighting NAT and firewalls, you tunnel out from where you already have access. Combined with autossh persistence, systemd hardening, and fail2ban protection, you get a production-ready remote access solution.
The scripts are available on GitHub. Clone, configure, and never get locked out again.