Skip to content
Go back

SOCKS5 Over SSH: Selective Routing Without a VPN

By SumGuy 8 min read
SOCKS5 Over SSH: Selective Routing Without a VPN

You Don’t Always Need a VPN

Someone locks down a service to their office network. You’re not at the office. Your first instinct is probably “spin up WireGuard” or “beg the IT guy to add my IP.” But here’s the thing — if you already have SSH access to a machine on that network, you’ve got everything you need.

ssh -D 1080 user@yourserver — that’s it. One flag, one port, and you’ve got a SOCKS5 proxy running on localhost:1080. No VPN client to install, no kernel module drama, no firewall rule negotiation. Point your browser (or curl, or git) at it and your traffic comes out the other side as if you were sitting at that machine.

This is selective routing. Your Spotify doesn’t go through the tunnel. Your apt update doesn’t go through the tunnel. Only the apps you tell go through the tunnel — which is exactly what you want when you’re proxying into a work network and you don’t want your entire internet connection running through your employer’s infrastructure. Everybody wins.

The Basics: ssh -D

Terminal window
ssh -D 1080 -C -N -f user@yourserver

Breaking that down:

You now have a SOCKS5 proxy at 127.0.0.1:1080. Anything that speaks SOCKS5 can use it. That’s a lot of things.

To verify it’s listening:

Terminal window
ss -tlnp | grep 1080

To kill it later:

Terminal window
pkill -f "ssh -D 1080"

The DNS Leak Gotcha: socks5h vs socks5

Before anything else — this is the gotcha that bites people. If you configure an app to use socks5://127.0.0.1:1080, the DNS lookup still happens locally before the traffic goes through the proxy. Your query for internal.corp.example leaks to your local resolver, which probably has no idea what that is.

The fix is socks5h — the h stands for “hostname forwarding.” DNS resolution happens on the remote side, through the tunnel.

# Wrong — DNS leaks locally
socks5://127.0.0.1:1080
# Right — DNS resolves on the remote server
socks5h://127.0.0.1:1080

This matters any time you’re accessing hostnames that only resolve inside the remote network. Keep socks5h in your muscle memory.

Browser: FoxyProxy and SwitchyOmega

The easiest win. Both extensions let you define proxy profiles and switch between them per-URL pattern.

FoxyProxy (Firefox and Chrome) — add a new proxy:

Then create a pattern like *.corp.example to only proxy matching URLs. Everything else hits your normal connection. It’s fast to toggle and you can have multiple profiles for multiple tunnels.

SwitchyOmega (Chrome/Edge) — same idea, more granular. You can set up auto-switch rules that check the URL against a PAC-style list. Useful if you’ve got a dozen internal hostnames to route.

Both extensions respect SOCKS5 DNS correctly as long as you enable the remote DNS option. Don’t forget that checkbox.

curl: —socks5-hostname

For curl, the flag to reach for is --socks5-hostname, not --socks5. Again, same DNS forwarding distinction.

Terminal window
curl --socks5-hostname 127.0.0.1:1080 http://internal.corp.example/api/health

Or set it in your environment for the session:

Terminal window
export ALL_PROXY=socks5h://127.0.0.1:1080
curl http://internal.corp.example/api/health

ALL_PROXY is respected by curl, wget, and a bunch of other tools. Set it, do your work, unset it when done.

git: proxy.command

Git doesn’t speak SOCKS natively, but you can tell it to use nc (netcat) as a proxy command:

~/.gitconfig
[http]
proxy = socks5h://127.0.0.1:1080
[core]
gitProxy = nc -X 5 -x 127.0.0.1:1080

The http.proxy setting covers HTTPS remotes. The gitProxy / proxy.command with nc covers native git protocol (the git:// scheme). -X 5 tells nc to use SOCKS5. -x is the proxy address.

For a single repo or a one-off:

Terminal window
GIT_SSH_COMMAND="ssh -o ProxyCommand='nc -X 5 -x 127.0.0.1:1080 %h %p'" \
git clone [email protected]:team/repo.git

Ugly but it works. The gitconfig approach is cleaner if you’re accessing internal repos regularly.

ProxyChains: For Stubborn Apps

Some apps just don’t have proxy settings. Electron apps, random CLI tools, that one Java binary from 2011 that you can’t recompile. ProxyChains wraps any program and forces its TCP connections through your proxy.

Install it:

Terminal window
sudo apt install proxychains4 # Debian/Ubuntu
sudo pacman -S proxychains-ng # Arch

Configure it:

/etc/proxychains4.conf
strict_chain
proxy_dns
quiet_mode
[ProxyList]
socks5 127.0.0.1 1080

proxy_dns handles the DNS-through-the-proxy bit. Then just prefix your command:

Terminal window
proxychains4 wget http://internal.corp.example/file.tar.gz
proxychains4 nmap -sT internal.corp.example

It’s not pretty — there’s overhead and some apps detect and reject it — but for the stubborn cases, it saves you from installing a full VPN just to run one command.

autossh + systemd: Keep the Tunnel Alive

The ssh -f background approach dies when the connection drops. For a tunnel you want always-on, combine autossh with a systemd user service.

Install autossh:

Terminal window
sudo apt install autossh

Create the service:

~/.config/systemd/user/ssh-tunnel-proxy.service
[Unit]
Description=SOCKS5 SSH Tunnel
After=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/autossh \
-M 0 \
-N \
-o "ServerAliveInterval=30" \
-o "ServerAliveCountMax=3" \
-o "ExitOnForwardFailure=yes" \
-D 1080 \
user@yourserver
Restart=always
RestartSec=10
[Install]
WantedBy=default.target

-M 0 disables autossh’s own monitoring port (we’re using SSH keepalives instead via ServerAliveInterval). ExitOnForwardFailure=yes makes sure autossh actually restarts when port binding fails rather than quietly running without the forward.

Enable and start:

Terminal window
systemctl --user daemon-reload
systemctl --user enable --now ssh-tunnel-proxy.service
systemctl --user status ssh-tunnel-proxy.service

Now your SOCKS5 proxy survives reboots, network drops, and laptop lid-closes. Your 2 AM self will appreciate it.

Multi-hop: SSH ProxyJump

Sometimes your target isn’t directly reachable — you need to go through a bastion/jump host first. ProxyJump handles this cleanly:

Terminal window

Or in your ssh config, which is cleaner:

~/.ssh/config
Host internal-target
HostName internal-host.corp.example
User user
ProxyJump bastion.corp.example
Host bastion.corp.example
User jumpuser
IdentityFile ~/.ssh/id_ed25519_bastion

Then your tunnel command becomes:

Terminal window
ssh -D 1080 -N -f internal-target

The full connection flows: your machine → bastion → internal host → SOCKS proxy back to you. From the outside, you’re coming from the internal host’s IP. This ProxyJump + bastion pattern is worth understanding deeply — we’ll dig into bastion host architecture properly in an upcoming article.

SOCKS5-over-SSH vs WireGuard vs Tailscale

Here’s the honest breakdown.

Use SOCKS5-over-SSH when:

Use WireGuard when:

Use Tailscale when:

The SOCKS5 approach shines in exactly the “I have SSH, I need to reach one thing, I don’t want to yak-shave a VPN setup” scenario. It also doesn’t require any elevated permissions on the remote host or any software installation. If you can SSH in, you can proxy. That’s the whole value proposition.

For permanent infrastructure where everyone on the team needs access, Tailscale or WireGuard are the right answer. For “I need to quickly check the internal Grafana dashboard from home,” ssh -D 1080 is done before you’ve finished installing WireGuard.

Quick Reference

SOCKS5 SSH tunnel cheat sheet
# Open tunnel
ssh -D 1080 -C -N -f user@host
# curl through tunnel (DNS via proxy)
curl --socks5-hostname 127.0.0.1:1080 http://target
# Set env for session
export ALL_PROXY=socks5h://127.0.0.1:1080
# Multi-hop
ssh -D 1080 -N -J jumphost user@target
# Kill tunnel
pkill -f "ssh -D 1080"

DNS leak rule: always socks5h, never socks5. Tunnel persistence: autossh + systemd. Browser switching: FoxyProxy with remote DNS enabled. The rest is just picking the right tool for the job.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Next Post
Argo Workflows vs Tekton

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts