Back to Articles

sshuttle: The VPN That's Just Python and SSH

[ View on GitHub ]

sshuttle: The VPN That's Just Python and SSH

Hook

What if you could turn any SSH connection into a full network VPN without touching a single config file on the remote server? That's exactly what sshuttle does, and it's been quietly solving connectivity problems for nearly a decade.

Context

Before sshuttle, developers faced an annoying trilemma when accessing remote networks. Option one: use SSH port forwarding with -L or -D flags, which meant manually mapping every service port and configuring SOCKS proxies for each application. Option two: set up a proper VPN like OpenVPN, which required admin access on the remote network, certificate management, and hours of configuration debugging. Option three: create an SSH tunnel and route traffic through it, which inevitably hit the TCP-over-TCP meltdown problem—when TCP packets are wrapped inside other TCP packets, congestion control algorithms conflict, causing catastrophic performance degradation.

Avery Pennarun created sshuttle to escape this trilemma. The insight was elegant: what if you could manipulate local firewall rules to transparently redirect traffic through an SSH connection, while using Python on the remote side to unwrap and forward those packets at the application layer? No remote admin access required. No VPN infrastructure to maintain. No TCP-over-TCP performance cliff. Just SSH, Python, and some clever packet manipulation. The tool became so popular that it spawned an active fork under the sshuttle organization (the original apenwarr/sshuttle repository now redirects there), accumulating nearly 9,000 stars and becoming the go-to solution for developers who need quick remote network access.

Technical Insight

Remote Server (SSH access only)

Local Machine (requires sudo)

modifies

intercepts TCP packets

extracts payload data

data stream

reconstructs TCP

response

serialized data

encrypted channel

Local Client Process

Local Firewall Rules

iptables/pf

SSH Connection

Remote Python Proxy

Destination Server

System architecture — auto-generated

At its core, sshuttle's architecture is deceptively simple but cleverly designed to avoid the pitfalls that plague other SSH-based VPN solutions. When you run sshuttle, it performs three key operations: it modifies your local firewall rules (iptables on Linux, pf on BSD/macOS), it establishes an SSH connection to the remote server, and it launches a Python proxy process on that server. The magic happens in how these components interact.

Here's a typical invocation:

# Route all traffic to 10.0.0.0/8 through the remote server
sudo sshuttle -r user@remote-server 10.0.0.0/8

# Or exclude certain subnets and include DNS proxying
sudo sshuttle -r user@remote-server 10.0.0.0/8 -x 10.0.1.0/24 --dns

# Route everything except local traffic (poor man's VPN)
sudo sshuttle -r user@remote-server 0.0.0.0/0 --exclude=10.0.0.0/8

When you execute this command, sshuttle creates firewall rules that intercept matching packets before they leave your machine. Instead of encapsulating these TCP packets inside an SSH tunnel (which would create TCP-over-TCP), sshuttle captures the TCP packets, extracts the payload data, and sends only the data through the SSH connection as a simple stream. On the remote side, the Python proxy receives these data chunks, reconstructs them into proper TCP connections to the destination servers, and forwards the responses back through SSH.

This design sidesteps TCP-over-TCP issues because you're not tunneling TCP packets—you're tunneling the application data. The outer SSH connection handles its own TCP flow control independently from the inner connections. It's the difference between shipping cars through a tunnel (TCP-over-TCP) versus disassembling them, shipping the parts, and reassembling on the other side (sshuttle's approach).

The firewall manipulation is platform-specific but follows similar logic. On Linux, sshuttle creates iptables rules in the nat table to redirect packets:

# Example of what sshuttle creates (simplified)
iptables -t nat -N sshuttle-12300
iptables -t nat -A OUTPUT -j sshuttle-12300
iptables -t nat -A sshuttle-12300 -d 10.0.0.0/8 -j REDIRECT --to-ports 12300

These rules redirect matching traffic to a local port where sshuttle's client process listens. The client then encodes this traffic into a simple protocol sent over SSH. On the remote end, a small Python script receives the encoded data, makes the actual connections, and returns responses. The beauty is that this remote Python script requires no special privileges—it just opens outbound connections like any normal application.

The DNS proxying feature (--dns) is particularly clever. sshuttle intercepts DNS queries locally, forwards them through the SSH connection, performs the lookup on the remote server, and returns the results. This ensures that internal hostnames resolve correctly and that your DNS queries don't leak to your local network's DNS servers. Under the hood, it uses the same packet interception mechanism but handles UDP DNS traffic specially, converting it to reliable transport over the SSH stream.

One architectural decision that makes sshuttle robust is its use of Python on both ends. While this adds overhead compared to a compiled solution, it provides several advantages: the same codebase runs on Linux, macOS, and BSD without modification; debugging is straightforward with standard Python tools; and deploying the remote component is trivial—sshuttle automatically sends the necessary Python code through SSH when connecting. No installation required on the remote server beyond having Python available.

Gotcha

The most significant limitation is that sshuttle only handles TCP traffic reliably. While there's experimental UDP support in recent versions, many applications requiring UDP (VoIP, gaming, some VPN protocols) won't work properly or at all. This makes sshuttle unsuitable as a complete network replacement for scenarios where UDP is critical. The DNS proxying helps with UDP DNS queries, but that's a special case—general UDP application traffic remains problematic.

Another gotcha involves the local root requirement. Because sshuttle modifies firewall rules and routing tables, it must run with elevated privileges on your local machine. This creates a security consideration: you're running a network-facing application as root, which expands the attack surface. While sshuttle's codebase is relatively small and auditable, this requirement makes it unsuitable for locked-down corporate environments where users don't have admin access to their machines. Additionally, the firewall manipulation can occasionally conflict with other VPN software or complex iptables configurations, requiring manual cleanup if sshuttle crashes without properly removing its rules. The tool attempts to clean up on exit, but unhandled exceptions or force-kills can leave your firewall in an inconsistent state. You'll want to know how to manually remove iptables rules or pf anchors before relying on sshuttle for critical connectivity.

Verdict

Use if: You need quick, transparent access to a remote network without setting up VPN infrastructure; you have SSH access but not admin rights on the remote server; you're primarily accessing web services, databases, and other TCP-based applications; you're a developer or DevOps engineer who frequently jumps between different remote environments; or you need a lightweight solution for temporary network access during debugging or development. Skip if: Your applications require reliable UDP support (gaming, VoIP, WireGuard); you need production-grade, always-on VPN connectivity with high availability; you cannot obtain local root/admin privileges on your client machine; you require maximum throughput for high-bandwidth applications where SSH overhead is unacceptable; or you need to support Windows clients (sshuttle is primarily Unix-focused). And remember: use the actively maintained fork at github.com/sshuttle/sshuttle, not the original apenwarr repository.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/apenwarr-sshuttle.svg)](https://starlog.is/api/badge-click/developer-tools/apenwarr-sshuttle)