Back to blog
Blog

Reverse SSH tunnel VPS access: a practical 2026 playbook for reaching private servers safely

Reverse SSH tunnel VPS access in 2026: reach private servers behind NAT with autossh, systemd, and hardened SSH settings.

By Anurag Singh
Updated on Apr 13, 2026
Category: Blog
Share article
Reverse SSH tunnel VPS access: a practical 2026 playbook for reaching private servers safely

Your server can be configured perfectly and still be unreachable. Maybe it’s stuck behind CGNAT, locked inside an office network, or sitting on a router you don’t control. That’s where reverse SSH tunnel VPS access pays off: the private host initiates an outbound SSH connection to a VPS you control, and you “come back in” through that VPS as if the private host had a public IP.

This is a practical 2026 playbook you can reuse for internal tools, CI runners, or a staging box at a client site. You’ll get real commands, systemd units, SSH hardening, verification checks, common failure modes, and a clean rollback.

Scenario (so the steps stay concrete)

We’ll use two machines:

  • Relay VPS (public IP): Debian 13, hostname relay01, SSH on 22.
  • Private host (no inbound access): Ubuntu 24.04 on a private network, hostname buildbox7. It can make outbound TCP connections to the internet.

Goal: from your laptop, SSH into the private host buildbox7 via the VPS, without opening ports on the private network.

Prerequisites and guardrails

  • A VPS with a public IPv4/IPv6 address and root access. If you want the simplest path, start with a HostMyCode VPS and keep it dedicated to relay traffic.
  • SSH access from the private host to the VPS (outbound). Port 22 is ideal; if your network blocks 22, you can run SSH on 443 later.
  • OpenSSH client on the private host and your laptop. (OpenSSH 9.x is typical in 2026 on Debian/Ubuntu.)
  • Optional but recommended: autossh on the private host for automatic reconnects.
  • A basic security posture. If your VPS isn’t hardened yet, review this hardening guide before exposing it as a relay.

Security warning: a reverse tunnel creates an inbound path toward a private network. Treat it like production access: least-privilege keys, locked-down SSH, and only the ports you actually need.

Step 1: Create a dedicated tunnel user on the VPS

On relay01 (the VPS), create a user that exists only to hold reverse tunnels.

sudo adduser --disabled-password --gecos "" tunnelbot
sudo usermod -aG ssh tunnelbot

Create the SSH directory with the right permissions:

sudo -u tunnelbot mkdir -p /home/tunnelbot/.ssh
sudo chmod 700 /home/tunnelbot/.ssh

Why this matters: you isolate tunnel keys and restrictions from human admin accounts. Key rotation and audits stay straightforward.

Step 2: Generate a key for the private host (and keep it scoped)

On buildbox7 (the private host), generate a dedicated SSH key for the tunnel. Use Ed25519 with a clear comment and no interactive password so systemd can bring the tunnel up unattended. If you require passphrases, you can use an agent, but you’ll be managing more moving parts.

sudo -i
ssh-keygen -t ed25519 -a 64 -f /root/.ssh/relay_reverse_tunnel_ed25519 -C "buildbox7-to-relay01-tunnel"

Expected output includes something like:

Generating public/private ed25519 key pair.
Your identification has been saved in /root/.ssh/relay_reverse_tunnel_ed25519
Your public key has been saved in /root/.ssh/relay_reverse_tunnel_ed25519.pub

If you prefer not to use root keys, create a dedicated service account on the private host (often required in regulated environments). For smaller internal setups, a root-owned key can be fine as long as the VPS side is tightly restricted (next step).

Step 3: Install the public key on the VPS with forced restrictions

Move the public key from buildbox7 to relay01. You can paste it by hand or use ssh-copy-id. Manual entry is useful here because you can add restrictions immediately.

On buildbox7:

cat /root/.ssh/relay_reverse_tunnel_ed25519.pub

On relay01, edit the authorized keys file:

sudo -u tunnelbot nano /home/tunnelbot/.ssh/authorized_keys

Add the key with restrictions that prevent shell access and limit what the key can do. Example (single long line):

restrict,port-forwarding,permitlisten="127.0.0.1:2222",command="/usr/sbin/nologin",no-agent-forwarding,no-X11-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... buildbox7-to-relay01-tunnel

Then set permissions:

sudo chown -R tunnelbot:tunnelbot /home/tunnelbot/.ssh
sudo chmod 600 /home/tunnelbot/.ssh/authorized_keys

What this does: the key can only set up port forwarding, can only bind the reverse listener to 127.0.0.1:2222 on the VPS (not the public interface), and cannot open a shell even if someone tries.

Step 4: Enable reverse forwarding safely on the VPS SSH server

On relay01, confirm sshd allows remote forwarding, but doesn’t let it bind to public interfaces by default.

Edit:

sudo nano /etc/ssh/sshd_config

Recommended settings for this pattern:

# Allow the tunnel feature
AllowTcpForwarding yes
GatewayPorts no
PermitTunnel no

# Keep sessions healthy
ClientAliveInterval 60
ClientAliveCountMax 3

Reload SSH safely:

sudo sshd -t && sudo systemctl reload ssh

Pitfall: avoid GatewayPorts yes unless you intentionally want the reverse port exposed to the internet. Here we keep it loopback-only and reach it through the VPS over SSH.

Step 5: Create the reverse tunnel from the private host (manual test)

Before you automate anything, run one manual tunnel to prove the wiring works. This maps VPS port 2222 (loopback only) to the private host’s SSH port 22.

sudo ssh -i /root/.ssh/relay_reverse_tunnel_ed25519 \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -N \
  -R 127.0.0.1:2222:127.0.0.1:22 \
  tunnelbot@relay01.example.net

What each piece means:

  • -N: no remote command; it’s a pure tunnel.
  • -R 127.0.0.1:2222:127.0.0.1:22: listen on the VPS loopback at 2222 and forward to the private host’s local port 22.
  • ServerAlive*: prevents “looks connected but is dead” sessions on flaky networks.

If it connects, the command will sit there without output. That’s expected.

Step 6: Verify the listener exists on the VPS

On relay01, confirm port 2222 is listening on loopback:

sudo ss -lntp | grep 2222

Expected output resembles:

LISTEN 0 128 127.0.0.1:2222 0.0.0.0:* users:(("sshd",pid=1234,fd=8))

If you don’t see it, head to “Common pitfalls” below.

Step 7: Connect from your laptop through the VPS to the private host

You have two solid options. Pick one, document it, and stick to it.

Option A (simple): SSH to the VPS and hop to localhost:2222

ssh admin@relay01.example.net
ssh -p 2222 builduser@127.0.0.1

This is straightforward and keeps the reverse port private to the VPS.

Option B (cleaner): one command using ProxyJump

From your laptop:

ssh -J admin@relay01.example.net -p 2222 builduser@127.0.0.1

If you do this often, put it into ~/.ssh/config (next step).

Step 8: Make it ergonomic with a dedicated SSH config stanza

On your laptop, edit ~/.ssh/config:

Host relay01
  HostName relay01.example.net
  User admin
  IdentityFile ~/.ssh/admin_relay01_ed25519

Host buildbox7-via-relay
  HostName 127.0.0.1
  Port 2222
  User builduser
  ProxyJump relay01
  ServerAliveInterval 30
  ServerAliveCountMax 3

Now you can run:

ssh buildbox7-via-relay

Verification: on the private host, check who logged in:

sudo last -a | head

Step 9: Keep the tunnel up with autossh + systemd (production pattern)

Manual tunnels fail at inconvenient times: ISP hiccups, Wi‑Fi drops, NAT timeouts. On Linux in 2026, the most predictable setup is autossh supervised by systemd.

Install autossh

On buildbox7:

sudo apt-get update
sudo apt-get install -y autossh

Create a systemd unit for the tunnel

Create /etc/systemd/system/reverse-tunnel-relay01.service on buildbox7:

sudo nano /etc/systemd/system/reverse-tunnel-relay01.service

Use this unit (adjust hostnames/users/ports):

[Unit]
Description=Reverse SSH tunnel to relay01 (buildbox7)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=root
Environment="AUTOSSH_GATETIME=0"
ExecStart=/usr/bin/autossh -M 0 \
  -N \
  -i /root/.ssh/relay_reverse_tunnel_ed25519 \
  -o "ExitOnForwardFailure=yes" \
  -o "ServerAliveInterval=30" \
  -o "ServerAliveCountMax=3" \
  -R 127.0.0.1:2222:127.0.0.1:22 \
  tunnelbot@relay01.example.net
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Enable and start it:

sudo systemctl daemon-reload
sudo systemctl enable --now reverse-tunnel-relay01.service

Check status and logs:

sudo systemctl status reverse-tunnel-relay01.service --no-pager
sudo journalctl -u reverse-tunnel-relay01.service -n 50 --no-pager

Expected signal: you should not see constant reconnect loops. If you do, a restriction or forwarding rule is blocking the session.

Step 10: Add guardrails (port choice, firewall, and SSH hardening)

You now have a working access path. The next step is making sure it stays quiet, predictable, and hard to misuse.

Keep reverse ports on loopback unless you explicitly need public exposure

We bound the reverse listener to 127.0.0.1:2222 on the VPS. That one choice eliminates a lot of noise, because internet scanners can’t touch the forwarded port.

If you must expose the reverse port publicly, do it intentionally

Sometimes you need a public callback port (a webhook receiver, a short-lived support window). If you go that route:

  • Bind to the VPS public interface (or 0.0.0.0) only for a short period.
  • Restrict with firewall rules to known source IPs.
  • Prefer a non-obvious port and log aggressively.

On Debian with nftables, a minimal allowlist example might look like:

sudo nft add rule inet filter input tcp dport 2222 ip saddr { 203.0.113.10 } accept

If you’re building broader host security posture, pair this with audit visibility. The workflow in this auditd monitoring post fits well with a relay VPS because it’s a natural target.

Move VPS SSH to a restricted surface (optional)

If your relay VPS is dedicated to tunnels, tighten SSH further: disable password auth, restrict users, and keep modern ciphers. If you want a baseline to compare against, the 2026 hardening guide covers the essentials.

Common pitfalls (and fast fixes)

  • The tunnel connects, but port 2222 doesn’t show up on the VPS.
    Check whether AllowTcpForwarding is disabled or overridden in /etc/ssh/sshd_config or /etc/ssh/sshd_config.d/*.conf. Validate with:
    sudo sshd -T | egrep 'allowtcpforwarding|gatewayports'
  • autossh keeps restarting every few seconds.
    Usually ExitOnForwardFailure is working as intended. Check logs:
    sudo journalctl -u reverse-tunnel-relay01.service -n 80 --no-pager
    Common causes: wrong key path, wrong username (tunnelbot), or the permitlisten restriction doesn’t match 127.0.0.1:2222.
  • You can SSH to the VPS but not to the forwarded port.
    Confirm the private host’s sshd is listening on localhost (or the right interface):
    sudo ss -lntp | grep ':22'
    If you’ve bound sshd to a specific interface, change the forward target from 127.0.0.1:22 to the correct local IP.
  • Corporate network blocks outbound 22.
    Run sshd on the VPS on 443 (or another permitted port) and connect the private host to that port. Keep your primary SSH locked down and document the exception.
  • The tunnel works for hours, then dies silently.
    NAT timeouts and stateful firewalls do this. ServerAliveInterval plus systemd restart usually fixes it. If it still drops, reduce the interval to 15 seconds.

Rollback plan (cleanly undo what you changed)

If you need to remove the access path quickly, do it in this order:

  1. On buildbox7, stop and disable the service:
    sudo systemctl disable --now reverse-tunnel-relay01.service
    sudo rm -f /etc/systemd/system/reverse-tunnel-relay01.service
    sudo systemctl daemon-reload
  2. On relay01, remove the key line from /home/tunnelbot/.ssh/authorized_keys and reload SSH:
    sudo -u tunnelbot nano /home/tunnelbot/.ssh/authorized_keys
    sudo systemctl reload ssh
  3. If you added firewall rules for a public reverse port, remove them (nftables) or revert your firewall config to the previous state.

After rollback, verify ss -lntp on the VPS shows no listener on 2222.

Next steps (make this pattern production-grade)

  • Use a second relay VPS for redundancy. One relay is a single point of failure. If the private host can hold two outbound tunnels, you can fail over by DNS or operator choice.
  • Add structured monitoring. At minimum, alert if the tunnel service restarts repeatedly or stays down for more than a few minutes. For a lightweight monitoring stack, you can also evaluate tools like in our Beszel monitoring tutorial.
  • Restrict what the tunnel can reach. Forward to a dedicated SSH port on the private host that only allows a maintenance user with forced commands, not full shell.
  • Rotate keys on a schedule. Treat tunnel keys like API tokens: scoped, rotated, and documented.

If you’re setting up a relay for remote access, pick a VPS with steady networking and predictable performance under long-lived SSH sessions. A dedicated HostMyCode VPS is a solid fit for relay duty, and managed VPS hosting makes sense if you’d rather hand off hardening, monitoring, and patching.

FAQ

Is reverse SSH tunnel VPS access safe for production?

Yes—if you run it like an access gateway: dedicated users, forced-command/no-shell keys, loopback-only listeners, and logging. The risky version is “open a public reverse port and forget it’s there.”

Should I use autossh or systemd alone?

Use both. autossh deals with unreliable networks; systemd supervises the process, restarts on failure, and records everything in journald.

Can I forward something other than SSH (like a web dashboard)?

Yes. For example, you can forward VPS 127.0.0.1:9009 to a private 127.0.0.1:3000 service. Keep it loopback-only on the VPS, then access it via an SSH hop or a VPN.

What’s the difference between a reverse tunnel and a VPN like WireGuard?

A reverse tunnel exposes specific ports through SSH. A VPN creates a routed network between peers. If you need broad east-west connectivity, a VPN usually wins. If you need one reliable path to one host behind NAT with minimal setup, a reverse tunnel is often simpler.

How do I prevent the VPS from becoming a lateral-movement beachhead?

Keep the reverse listener on 127.0.0.1, restrict who can SSH into the VPS, and monitor authentication and forwarding activity. For multi-tenant setups that need deeper segmentation, study patterns like those in our WireGuard segmentation tutorial.

Summary

reverse SSH tunnel VPS access solves a specific problem: reaching machines you can’t expose to the internet. The dependable pattern is simple—use a locked-down tunnel user on the VPS, a restricted key that can only open a loopback reverse port, and a systemd-managed autossh service on the private host. Set it up once, and you stop spending your evenings fighting NAT.

If you want a clean relay setup with predictable performance, start with a stable HostMyCode VPS and keep it dedicated to access and ops traffic.

Reverse SSH tunnel VPS access: a practical 2026 playbook for reaching private servers safely | HostMyCode