Back to Articles

sshuttle: The Transparent Proxy That Turns SSH Into a VPN Without the VPN Overhead

[ View on GitHub ]

sshuttle: The Transparent Proxy That Turns SSH Into a VPN Without the VPN Overhead

Hook

What if you could VPN into any network where you have SSH access, without installing a single piece of software on the remote side? That's exactly what sshuttle does—and it does it by hijacking your local firewall.

Context

Every developer has been there: you need to access a database on a corporate network, hit an internal API, or browse through a restrictive firewall. The traditional solution is a VPN, but VPNs require infrastructure—OpenVPN servers, WireGuard configurations, admin privileges on both ends, and often a dedicated IT team to manage certificates and routing tables. SSH port forwarding offers an alternative, but it's clunky. You need to configure SOCKS proxies in every application, remember port numbers, and deal with software that simply won't work through a proxy.

sshuttle emerged from this frustration. Created by Avery Pennarun (who later went on to work on Tailscale), it asked a simple question: what if we could make SSH behave like a VPN without actually being a VPN? The insight was that most remote access scenarios don't need the full complexity of VPN infrastructure. You already have SSH access to a remote machine. You already have Python installed on both ends. Why not leverage what's already there? The result is a tool that requires zero server-side configuration, no kernel modules, and no special privileges on the remote end—just an SSH connection and the ability to modify your local firewall rules.

Technical Insight

Remote Server

Local Machine

Redirected TCP packets

Serialized connection data

SSH transport layer

Reconstructed TCP connections

DNS queries

DNS queries

DNS responses

DNS responses

Modifies rules at startup

sshuttle Client

Local Firewall

iptables/pf/nftables

SSH Connection

sshuttle Server

Remote Python Process

Destination Services

DNS Resolver

System architecture — auto-generated

The magic of sshuttle lies in how it manipulates network traffic at the firewall level while avoiding the performance pitfalls of traditional SSH tunneling. When you run sshuttle, it does something clever: it modifies your local firewall (iptables on Linux, pf on macOS/BSD, or nftables on modern Linux systems) to redirect outbound TCP connections to itself. Then it forwards these connections over SSH to the remote end, where a simple Python server reconstructs and forwards them to their actual destinations.

Here's a basic usage example that tunnels all traffic destined for the 10.0.0.0/8 network through a remote server:

# Tunnel a specific subnet
sshuttle -r user@remote-server 10.0.0.0/8

# Tunnel multiple subnets, including DNS
sshuttle -r user@remote-server --dns 10.0.0.0/8 172.16.0.0/12

# Tunnel everything except local networks (auto-detect)
sshuttle -r user@remote-server 0/0 --exclude 192.168.1.0/24

# Use with SSH config and verbose logging
sshuttle -r myserver 10.0.0.0/8 -vv

What's remarkable is what happens under the hood. Traditional SSH port forwarding creates TCP-over-TCP situations—you're tunneling TCP connections inside another TCP connection. This causes severe performance degradation because TCP's congestion control and retry logic interact poorly when nested. If the outer TCP connection experiences packet loss, the inner TCP connection also tries to retransmit, leading to exponential backoff and glacial speeds.

sshuttle sidesteps this by operating differently. Instead of creating a TCP tunnel and running TCP through it, sshuttle uses SSH as a multiplexed transport layer for raw packet data. The client intercepts TCP packets before they enter the TCP stack, serializes the connection state, and sends this state information over SSH. The remote end reconstructs the TCP connections as if they originated there. This architecture means you're not actually tunneling TCP inside TCP—you're tunneling connection metadata.

The firewall manipulation is platform-specific. On Linux with iptables, sshuttle creates rules in the nat table:

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

These rules intercept packets destined for your specified subnets and redirect them to sshuttle's local listening port. The client then reads these packets using a raw socket, extracts the destination information from the TCP headers, and coordinates with the remote end to establish the actual connection. When data comes back from the remote server, sshuttle injects it back into the local network stack as if it came from the original destination.

DNS handling deserves special attention. Without DNS tunneling, your local machine performs DNS lookups that might leak information or fail to resolve internal hostnames. With the --dns flag, sshuttle intercepts UDP port 53 traffic and forwards DNS queries through the tunnel. On the remote end, these queries are resolved using the remote machine's DNS resolver, ensuring you get internal IP addresses and maintain DNS privacy.

The server-side component is refreshingly simple—it's just a Python script that sshuttle uploads and executes via SSH. No installation, no configuration files, no persistent services. When you disconnect, the remote side cleans up automatically. This is the "poor man's VPN" philosophy in action: leverage existing infrastructure (SSH, Python) rather than building parallel systems.

Gotcha

Despite its elegance, sshuttle has real limitations that will bite you in specific scenarios. The most significant is UDP support—or rather, the lack of it for anything except DNS. If your application uses UDP for transport (VoIP, video conferencing, many games, DNS-over-HTTPS that doesn't fall back to TCP, some database protocols), it simply won't work through sshuttle. The traffic will either fail silently or bypass the tunnel entirely, depending on your firewall rules. This isn't a bug; it's an architectural constraint. UDP is connectionless, and sshuttle's model of intercepting connection state doesn't translate to UDP's fire-and-forget model.

The root requirement on the client side surprises many users who are drawn to sshuttle specifically because it doesn't require admin rights on the server. You do need root or administrator privileges on your local machine to modify firewall rules. This makes sshuttle unsuitable for scenarios where you're working on a locked-down corporate laptop or shared development machine. Additionally, the firewall manipulation can conflict with other networking tools. If you're running Docker, Kubernetes, or other virtualization platforms that manage their own iptables rules, sshuttle can cause routing conflicts that are painful to debug. Performance is another consideration. While sshuttle avoids the worst TCP-over-TCP pathologies, it's still doing packet processing in userspace Python code. For casual browsing or database queries, this is fine. For sustained high-throughput workloads—large file transfers, video streaming, high-frequency trading data—you'll notice the overhead compared to kernel-level VPN implementations like WireGuard. Finally, IPv6 support is present but not as mature as IPv4, and the interaction between IPv4 and IPv6 routing can be counterintuitive when tunneling both.

Verdict

Use if: You have SSH access to a remote network but lack VPN infrastructure or admin rights on the remote side. You need transparent network access without configuring SOCKS proxies in every application. You're tunneling primarily web traffic, database connections, or other TCP-based protocols. You're working from a personal laptop where you have root access. You need a temporary solution that's fast to set up and leaves no server-side traces. Skip if: Your applications require UDP support beyond DNS (gaming, VoIP, VPN-over-VPN scenarios). You're on a locked-down client machine without admin rights. You already have WireGuard or a well-configured VPN and need maximum performance. You're running complex Docker or Kubernetes setups where iptables conflicts would be a nightmare. Your organization has security policies that prohibit local firewall manipulation. For quick-and-dirty remote network access with nothing but SSH, sshuttle is unbeatable—just know its boundaries.

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