Shell access to a box that lives behind NAT is one of those problems with a dozen answers and no single right one. Which one fits depends on the network you are crossing, how much you control, and how much setup you will tolerate. Below, five common approaches are weighed against each other so you can pick deliberately rather than by habit.
What You Are Actually Up Against
A NAT’d host exposes no routable address on any interface. When it opens a connection outward, the gateway rewrites the source so the reply can come back; but nothing outside can initiate a connection inward unless the gateway has been told to forward a port — and that gateway is frequently not yours to configure.
Every method that follows sidesteps this by treating an outbound connection from the box as the transport, so an inbound port is never required on the machine you want to reach.
Method 1: Reverse SSH Tunnel with autossh
How it works: The remote box opens an SSH connection to a jump host you control and binds a remote port (-R) that tunnels back to its own SSH listener. You then reach the box by SSH-ing to that forwarded port on the jump host from wherever you happen to be.
# On target — creates persistent reverse tunnel
autossh -M 0 -N -R 2222:localhost:22 \
-o ServerAliveInterval=30 \
user@jump.example.com
Reliability: Very high. SSH is the most battle-tested protocol in existence. autossh monitors the tunnel and restarts it on failure.
Firewall bypass: Excellent on port 443. SSH traffic on port 22 is often blocked; running it over 443 is common and effective.
Downsides: Requires an existing SSH key installed on the jump server before setup. Can't bootstrap from zero — you need initial access to place the key. The target needs SSH client installed.
Best for: Situations where you already have some access to set up the key, or managed machines where you can pre-configure keys during provisioning.
Method 2: Self-Hosted Secret-Key Relay (GG-Socket)
How it works: You run a small Go relay on your own VPS. Both peers dial it over TLS on port 443, each presenting a shared secret. The relay pairs the first two peers with matching secrets and opposite roles, then splices their streams into a tunnel. No SSH keys need to exist beforehand — the secret is the only credential the relay checks.
# Deploy the relay once
docker compose up -d --build
# Each peer dials the same secret
gs-client GGK-YOUR-SECRET -r r.ggsocket.com:443
Reliability: High and entirely in your hands. Uptime is a function of your own VPS, not a vendor’s status page, and the relay is a single lightweight process that is trivial to restart.
Firewall bypass: Excellent. The relay speaks plain TLS on 443, so peer connections look like ordinary HTTPS to a network inspector.
Downsides: You operate the relay yourself — a VPS and a domain are prerequisites. Traffic transits your relay, though it only ever sees encrypted TLS and the secret used for matching.
Best for: Situations where you want full ownership of the rendezvous point, configurable limits, and traffic that never touches a third party — without pre-distributing SSH keys.
Method 3: WireGuard Mesh (Tailscale / Headscale)
How it works: WireGuard builds an encrypted point-to-point overlay between machines, and Tailscale — or its self-hosted sibling Headscale — layers key exchange and NAT traversal on top. Once joined, every machine is reachable across the overlay by a stable mesh address in the 100.x.x.x range.
# On each machine — join the mesh
tailscale up --login-server=https://headscale.example.com
# SSH directly to mesh IP — no relay
ssh user@100.64.0.3
Reliability: Excellent. WireGuard is a kernel-level VPN. Once machines are in the mesh, they're persistently reachable as long as any mesh peer is online. Headscale (self-hosted) eliminates the Tailscale.com dependency.
Firewall bypass: Good on most networks (UDP). Strict firewalls that block all non-TCP traffic will break WireGuard. Tailscale has a fallback relay (DERP) for this case.
Downsides: More complex initial setup. Managing a Headscale instance adds operational overhead. Better suited for managing a fleet of machines than ad-hoc access to individual machines.
Best for: Organizations managing 10+ machines long-term. The mesh model scales better than per-machine secrets.
Method 4: Commercial Tunnel (Cloudflare Tunnel, ngrok)
How it works: A daemon — cloudflared or ngrok — runs on the box and dials the provider’s edge, which then hands you a hostname or URL to connect through. The provider’s relay is the middleman for every session.
# Cloudflare Tunnel
cloudflared tunnel --url ssh://localhost:22
# ngrok
ngrok tcp 22
Reliability: Very high uptime from both providers. Cloudflare Tunnel in particular is extremely stable.
Firewall bypass: Excellent — both use HTTPS/WebSocket outbound connections.
Downsides: Your sessions go through a third-party provider. ngrok free tier rate-limits and exposes a random URL per session. Cloudflare Tunnel requires a Cloudflare account and domain. Neither is fully self-hosted.
Best for: Quick access with zero infrastructure. Not suitable when session privacy matters or when you need no third-party dependency.
Method 5: DNS Tunnel (iodine)
How it works: TCP data is encoded into DNS queries. The box fires queries at a subdomain you own (say t.example.com); your authoritative DNS server (iodined) decodes the requests and answers with response packets, and a virtual tunnel interface stands up on both ends.
# Target (client side)
iodine -f -P password t.example.com
# Then SSH through the tunnel
ssh -p 22 user@192.168.99.1
Reliability: Low-to-medium. DNS-based tunnels are inherently slow (1–3 KB/s) and can break when DNS resolvers cache responses aggressively or rate-limit queries.
Firewall bypass: Exceptional. If DNS works at all, iodine works. This is the only method that survives networks that block all TCP except DNS UDP/53.
Downsides: Very slow. Requires control of a DNS zone. Complex setup. SSH is barely usable at tunnel speeds — sufficient for emergency access, not for regular use.
Best for: Last-resort access on maximum-restriction networks (captive portal WiFi with DNS-only access). Not a primary method.
Comparison Table
| Method | Reliability | Firewall bypass | Zero-touch install | Self-hosted | Speed |
|---|---|---|---|---|---|
| autossh reverse tunnel | ★★★★★ | ★★★★☆ (port 443) | ✗ Needs pre-placed key | ✓ | Full SSH speed |
| GG-Socket relay | ★★★★★ | ★★★★★ (TLS/443) | ✓ Shared secret only | ✓ | Full SSH speed |
| WireGuard mesh | ★★★★★ | ★★★★☆ (UDP) | ✗ Agent pre-install | ✓ (Headscale) | Native WireGuard |
| Cloudflare / ngrok | ★★★★★ | ★★★★★ | ✓ | ✗ Third-party | Full speed |
| DNS tunnel (iodine) | ★★★☆☆ | ★★★★★ (DNS only) | ✗ Complex setup | ✓ | ~2 KB/s |
Which Method Should You Use?
Reach for GG-Socket when ownership of the rendezvous point matters more than zero setup — you run the relay, you set the limits, and the traffic stays on infrastructure you control.
Fall back to autossh when you already have a key on a jump host and want the most boring, battle-proven transport available. The two complement each other rather than compete.
Pick WireGuard (under Headscale) when the goal is a persistent private network across many machines rather than ad-hoc access to a single box.
Hold a DNS tunnel in reserve for the rare network that blocks every outbound TCP flow except DNS. You will recognise that situation when you meet it.
Run the relay yourself.
Your VPS, your limits, your traffic — no third party in the path.
docker compose up -d --buildGet Started →