sshuttle: The Poor Man's VPN That Tunnels Through Plain SSH

2026-05-24

You have SSH access to a jumpbox sitting inside a private network. From your laptop you want to reach the database at 10.4.7.22, the internal wiki at wiki.corp.lan, and three Kubernetes services nobody bothered to expose. The classic answers all hurt:

sshuttle solves this. It needs only SSH access and a working Python on the remote side. No root on the server. No tun/tap. It rewrites your local firewall (iptables on Linux, pf on macOS) to redirect matching traffic into a TCP-over-SSH session, where a Python helper on the remote replays it onto the target network.

# Forward the entire internal corp network through a bastion
sshuttle -r bastion.corp.example 10.0.0.0/8 192.168.0.0/16

# Forward EVERYTHING — full default-route VPN
sshuttle -r me@bastion 0.0.0.0/0

# Include DNS so internal hostnames resolve via the remote
sshuttle --dns -r me@bastion 0/0

# Forward a subnet but punch a hole for your local LAN
sshuttle -r me@bastion 0/0 -x 192.168.1.0/24 -x 127.0.0.0/8

# Auto-detect remote subnets and forward only those
sshuttle -Nr me@bastion

The killer trick is -N: it reads /proc/net/route on the remote and tunnels exactly the subnets that side is connected to. No more "what's the CIDR again?"

Why it beats ssh -D: SOCKS only works for applications that explicitly use it. sshuttle works for everything — your browser, psql, kubectl, mosquitto_sub, a random Go binary — because it intercepts at the kernel level. Your apps don't know they're being tunneled.

Why it beats real VPNs: no admin privileges on the remote. If you can ssh user@host and run python3, you have a VPN. Sudo is required locally (to write firewall rules), but that's it.

The TCP-over-TCP fix: a naive SSH tunnel runs TCP inside TCP, which produces ugly stalls when both layers retransmit. sshuttle terminates TCP locally, sends a stream of frames to the remote, and the remote opens fresh TCP connections to the targets. Latency is real but the pathological collapse case is gone.

Practical session:

# In a separate terminal — sshuttle holds the foreground
$ sshuttle --dns -Nr [email protected]
client: Connected.

# In another shell, just use the network like you're inside it
$ dig +short db-master.corp.lan
10.4.7.22
$ psql -h db-master.corp.lan -U analytics
$ kubectl --context=corp get pods
$ curl https://wiki.corp.lan/

Ctrl-C tears down the firewall rules cleanly. There is no daemon to leak, no config file to forget, no certificate to rotate. When the bastion goes down, sshuttle exits and your laptop is back to normal in milliseconds.

Two warts worth knowing: it can't tunnel UDP without --method=tproxy (root on the remote, defeats the point), and on macOS recent OS updates occasionally break pf integration — pin a known-good version. Otherwise, it has been the same five-flag tool since 2010.

Key Takeaway: If you have SSH and Python on a box, you already have a working transparent VPN — sshuttle just glues your local firewall to it without ever needing root on the remote.

All newsletters