Cross-platform CLI that manages an SSH-based SOCKS5 tunnel so Firefox can bypass corporate VPNs (GlobalProtect, AnyConnect, Zscaler, ...)
Project description
ssh-socks-cli
Route Firefox traffic around your corporate VPN via an SSH SOCKS5 tunnel — without wrestling with
ssh -D, autossh flags, or Firefox's proxy pane.
What problem does this solve?
You work on a corporate laptop with an always-on VPN — Palo Alto GlobalProtect, Cisco AnyConnect, Zscaler, or similar. Your employer routes every byte of traffic through their gateway, and you'd rather keep personal browsing (webmail, banking, social media, geo-restricted streaming) outside that tunnel. Or you're testing a service as an external user from the same machine. Or you just want separation between work and personal contexts.
The traditional workaround is:
- SSH into a personal VPS or home server with
ssh -D 1080 user@host - Open Firefox's network settings and manually configure SOCKS5 on 127.0.0.1:1080
- Remember to enable "Proxy DNS when using SOCKS v5" (or leak your DNS through the corporate resolver)
- Do it all over again every time you reboot
ssh-socks-cli automates all of that in one CLI.
⚠️ You need an SSH exit server
This tool is useless without one. ssh-socks-cli is a client — it manages the connection from your laptop to an SSH server you already own, and tunnels Firefox traffic through it. The server is the part that actually exits to the internet on your behalf. Your Firefox's public IP becomes whatever that server's public IP is.
Any of the following works, as long as it runs sshd and is reachable from your laptop:
- A VPS (DigitalOcean, Hetzner, Linode, OVH, Vultr, AWS Lightsail, Fly.io, …). $3–6/month is plenty. Most reliable option, works from anywhere you have internet.
- A Raspberry Pi running 24/7 at home. Requires your router to port-forward SSH (and ideally a dynamic-DNS service like DuckDNS or Cloudflare Tunnel if your ISP doesn't give you a static IP). An RPi Zero 2W is enough — this workload is tiny.
- A home server / NAS with SSH exposed (Synology, Unraid, TrueNAS, …).
- An old laptop or mini-PC left plugged in at home running Linux.
- A friend's server where you have an account — only if you trust them, because all your Firefox traffic will exit from there.
What the server needs:
sshdrunning and reachable on some port (22 or custom).- Your SSH public key in
~/.ssh/authorized_keysfor the user you'll connect as. - Outbound internet access.
- That's it. No extra software, no SOCKS5 daemon — OpenSSH's
-Dflag is a SOCKS5 server, built in.
What the server does NOT need:
- A public static IP (dynamic DNS works).
- A SOCKS5 server, Shadowsocks, V2Ray, or anything like that.
- Significant resources. An RPi Zero 2W or a $3/month VPS handles this trivially.
Before running ssh-socks init, verify you can already SSH in manually:
ssh user@your-server.example.com
# If that works, you're ready. If it doesn't, fix your SSH access first —
# ssh-socks-cli cannot help you debug an unreachable server.
How it works
Before — corporate VPN captures everything
flowchart LR
subgraph laptop["💻 Corporate laptop"]
FF["Firefox"]
Other["Chrome / Slack /<br/>Outlook / git / ..."]
end
VPN["🛡️ Corporate VPN<br/>(GlobalProtect / AnyConnect / Zscaler)"]
Net["🌐 Internet"]
FF --> VPN
Other --> VPN
VPN -->|"logs + DNS<br/>+ filters"| Net
Every byte your laptop sends goes through the corporate gateway. Your employer sees all DNS lookups, can block content, and logs connections.
After — Firefox exits through your own server
flowchart LR
subgraph laptop["💻 Corporate laptop"]
FF["Firefox"]
CLI["ssh-socks-cli<br/>(autossh)<br/>127.0.0.1:1080"]
Other["Chrome / Slack /<br/>Outlook / git / ..."]
end
VPS["🖥️ Your SSH server<br/>(VPS / RPi / home server)"]
VPN["🛡️ Corporate VPN"]
Personal["🌐 Personal internet"]
Corp["🏢 Corporate resources"]
FF -->|"SOCKS5"| CLI
CLI ==>|"encrypted<br/>SSH tunnel"| VPS
VPS --> Personal
Other --> VPN
VPN --> Corp
style CLI fill:#2d5f2d,color:#fff,stroke:#4caf50
style VPS fill:#1a3a5c,color:#fff,stroke:#2196f3
Only Firefox is re-routed. Every other app (Chrome, Slack, Outlook, git, your IDE) keeps using the corporate VPN exactly as before. The corporate VPN is not bypassed for those — this is a split-tunnel, not a full bypass.
What happens on a single Firefox request
sequenceDiagram
autonumber
participant FF as Firefox
participant Tun as ssh-socks-cli<br/>(local SOCKS5<br/>127.0.0.1:1080)
participant VPS as Your SSH server
participant Site as reddit.com
FF->>Tun: SOCKS5: connect to<br/>"reddit.com:443"<br/>(hostname, not IP)
Note over Tun: Tunneled inside the<br/>encrypted SSH connection
Tun->>VPS: forwarded SOCKS5 request
VPS->>VPS: DNS resolve "reddit.com"<br/>(using VPS's resolver,<br/>NOT corporate DNS)
VPS->>Site: TCP connect to<br/>151.101.1.140:443
Site-->>VPS: TLS handshake
VPS-->>Tun: bytes over SSH
Tun-->>FF: bytes over SOCKS5
Note over Site: Sees your VPS's IP,<br/>not your laptop's
Key point: the hostname is resolved at the VPS, not at your laptop. That's what network.proxy.socks_remote_dns = true buys you — without it, Firefox would ask the local OS resolver (= corporate DNS pushed by GlobalProtect) before sending the request through the tunnel, leaking every domain you visit to your employer.
Features
- One-command tunnel lifecycle —
start,stop,status,restart,logs - Firefox integration — writes the correct
user.jspreferences with DNS-leak protection and WebRTC hardening, automatically detecting your default profile - Auto-reconnect via
autosshwhen available, falling back to plainsshwith aggressive keep-alives doctorcommand — diagnoses missing binaries, key file permissions, host reachability, and best-effort corporate VPN detection- XDG-compliant config — lives at
~/.config/ssh-socks-cli/config.toml(Linux/macOS) or%APPDATA%\ssh-socks-cli\(Windows) - Zero heavy dependencies — no
paramiko, nocryptography. Shells out to systemssh/autossh - Cross-platform — macOS, Linux, Windows (native OpenSSH client)
Prerequisites
On your laptop:
- Python 3.11+
ssh(OpenSSH client) — any modern macOS, Linux, or Windows 10/11 already has itautossh(optional but strongly recommended) — for automatic reconnection when your network flaps- macOS:
brew install autossh - Debian/Ubuntu:
sudo apt install autossh - Fedora/RHEL:
sudo dnf install autossh - Arch:
sudo pacman -S autossh
- macOS:
- Firefox (obviously) — other browsers are out of scope for v0.1
On your exit server: see the SSH exit server section above. TL;DR: anything running sshd that you can SSH into manually — a VPS, a Raspberry Pi at home, a NAS, whatever.
Installation
pip install ssh-socks-cli
Or from source:
git clone https://github.com/sergioarojasm98/ssh-socks-cli.git
cd ssh-socks-cli
pip install -e .
Quick start
# 1. Interactive setup — writes ~/.config/ssh-socks-cli/config.toml
ssh-socks init
# 2. Verify your environment is ready
ssh-socks doctor
# 3. Start the tunnel in the background
ssh-socks start
# 4. Configure Firefox to use it (detects default profile automatically)
ssh-socks firefox apply
# 5. Restart Firefox — done. Your Firefox traffic now exits from your SSH server.
To verify it's working, visit a site like https://ifconfig.me in Firefox. The IP should match your SSH server, not your corporate gateway.
Command reference
| Command | Description |
|---|---|
ssh-socks init |
Interactively create the config file |
ssh-socks start |
Start the SOCKS5 tunnel in the background |
ssh-socks stop |
Stop the running tunnel |
ssh-socks restart |
Stop + start |
ssh-socks status |
Show tunnel status (running/stopped, PID, endpoint) |
ssh-socks logs [-f] [-n N] |
Show tunnel log (tail or follow) |
ssh-socks doctor |
Run environment diagnostics |
ssh-socks config show |
Print current configuration |
ssh-socks config path |
Print config file path |
ssh-socks firefox show |
Print the user.js block to stdout |
ssh-socks firefox apply |
Inject the block into the default profile's user.js |
ssh-socks firefox reset |
Replace the apply block with a defaults-restoring block |
ssh-socks firefox purge |
Remove any ssh-socks-cli block from user.js entirely |
ssh-socks firefox profiles |
List detected Firefox profiles |
ssh-socks --version |
Show version |
Configuration file
Example ~/.config/ssh-socks-cli/config.toml:
[tunnel]
host = "proxy.example.com"
user = "sergio"
port = 22
identity_file = "~/.ssh/id_ed25519"
local_port = 1080
bind_address = "127.0.0.1"
compression = true
server_alive_interval = 30
server_alive_count_max = 3
connect_timeout = 10
strict_host_key_checking = "accept-new"
# use_autossh = true # omit to auto-detect
[firefox]
proxy_dns = true # critical: prevents DNS leaks through corporate DNS
bypass_list = "localhost, 127.0.0.1"
disable_webrtc = true # WebRTC can leak your real IP even through SOCKS5
What does the Firefox block actually do?
ssh-socks firefox apply injects a clearly-delimited block into your profile's user.js:
// BEGIN ssh-socks-cli managed block (do not edit manually)
user_pref("network.proxy.type", 1);
user_pref("network.proxy.socks", "127.0.0.1");
user_pref("network.proxy.socks_port", 1080);
user_pref("network.proxy.socks_version", 5);
// DNS leak prevention (both old and Firefox-128+ pref names)
user_pref("network.proxy.socks_remote_dns", true);
user_pref("network.proxy.socks5_remote_dns", true);
user_pref("network.dns.disablePrefetch", true);
user_pref("network.dns.disablePrefetchFromHTTPS", true);
// Force proxy, never fall back to direct (which would leak via the VPN)
user_pref("network.proxy.failover_direct", false);
user_pref("network.proxy.allow_bypass", false);
// Disable DoH (TRR) — would race the SOCKS tunnel and leak DNS
user_pref("network.trr.mode", 5);
// Disable speculative connections / prefetch (they bypass the proxy)
user_pref("network.http.speculative-parallel-limit", 0);
user_pref("network.predictor.enabled", false);
user_pref("network.prefetch-next", false);
// Disable captive portal / connectivity probes that hit Mozilla directly
user_pref("network.captive-portal-service.enabled", false);
user_pref("network.connectivity-service.enabled", false);
// WebRTC leak prevention
user_pref("media.peerconnection.enabled", false);
// END ssh-socks-cli managed block
Each block is there for a reason:
| Pref | Why |
|---|---|
network.proxy.type=1 |
Enable manual proxy mode |
network.proxy.socks* |
Point Firefox at 127.0.0.1:1080 as SOCKS v5 |
network.proxy.socks_remote_dns + socks5_remote_dns |
Critical — DNS resolution happens at the SSH server, not through your corporate resolver. Firefox 128 introduced a new pref name; we set both. |
network.trr.mode=5 |
Critical — disable DNS-over-HTTPS. Otherwise Firefox would race TRR against the SOCKS tunnel and leak DNS. |
network.proxy.failover_direct=false + allow_bypass=false |
If the tunnel drops, Firefox will NOT silently fall back to direct (i.e., the VPN). |
network.http.speculative-parallel-limit=0, predictor.enabled=false, prefetch-next=false |
Firefox opens speculative TCP connections before the proxy is consulted; disable them. |
network.captive-portal-service.enabled=false |
Firefox pings detectportal.firefox.com directly on startup, bypassing the proxy. |
media.peerconnection.enabled=false |
WebRTC can bypass the SOCKS proxy and leak your real LAN IP. |
Reverting Firefox changes (two-step)
Firefox copies user_pref values into prefs.js at shutdown, so simply deleting the apply block from user.js is not enough to undo the previous session's changes. The rollback is therefore a two-step operation:
ssh-socks firefox reset— replaces the apply block with a defaults-restoring block that sets every touched pref back to its Firefox factory value. Restart Firefox once. On that restart, Firefox overwritesprefs.jswith the defaults.ssh-socks firefox purge— removes the block fromuser.jsentirely, leaving the rest of your prefs intact.
Your existing user.js is backed up to user.js.sshsocks-backup-<timestamp> before every write.
Security notes
- This is not a VPN. It's a SOCKS5 proxy for Firefox only. Other apps on your machine still use the corporate VPN unless you configure them separately.
- You control the exit server. All Firefox traffic goes through your SSH host. Pick a host you trust.
- Corporate policy. Check whether your employer permits split-tunneling. This tool does not try to hide its existence — if your company runs DLP/EDR, they can likely see that you have an SSH session open. Use at your own discretion.
- Key file permissions.
ssh-socks doctorwill warn you if your private key has loose permissions. Fix withchmod 600 ~/.ssh/id_ed25519. - Host key checking. Defaults to
accept-new(TOFU on first connect, strict thereafter). You can tighten this in the config.
Troubleshooting
ssh-socks start exits immediately.
Run ssh-socks logs — the SSH error is captured verbatim. The most common causes are wrong host/user, bad key, or the local port already in use.
Firefox still uses the corporate VPN.
- Did you actually restart Firefox after
ssh-socks firefox apply?user.jsis only read on startup. - Go to
about:preferences#general→ Network Settings. It should show "Manual proxy configuration" with SOCKS host127.0.0.1and port1080. - Visit
about:configand searchnetwork.proxy.socks— confirm the values are what you expect.
DNS still resolves through my company.
Confirm network.proxy.socks_remote_dns is true in about:config. Visit https://dnsleaktest.com in Firefox to confirm.
The tunnel drops every few minutes.
Install autossh — it will automatically reconnect. Check ssh-socks doctor to confirm it was picked up.
Comparison with alternatives
| Option | Pros | Cons |
|---|---|---|
Manual ssh -D 1080 |
Zero install | Forget on reboot, no status, no Firefox help, no auto-reconnect |
| FoxyProxy extension | Rich per-URL routing | You still need to run the SSH tunnel yourself |
ssh-socks-cli |
Tunnel lifecycle + Firefox config + doctor in one tool | You need an SSH server |
| Commercial VPN | Any browser, any traffic | Monthly cost, another company in your traffic path |
Development
git clone https://github.com/sergioarojasm98/ssh-socks-cli.git
cd ssh-socks-cli
pip install -e ".[dev]"
pytest
ruff check .
mypy src
License
MIT — see LICENSE.
Author
Built by Sergio Rojas — Senior Technical & AI Ops Engineer who got tired of typing ssh -D 1080 every morning.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ssh_socks_cli-0.1.0.tar.gz.
File metadata
- Download URL: ssh_socks_cli-0.1.0.tar.gz
- Upload date:
- Size: 29.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ec85b50536ba8efeed472cd10dbe82ceae4c8417bc0aa1e8d6b28e0c0e8d8e8
|
|
| MD5 |
bcf3b31261c385888bdb2ea38ba81a87
|
|
| BLAKE2b-256 |
94b17dcd1206cb96b1c6b34346ea4b699fdac9b6c0c696ac73c9e8a270450cef
|
Provenance
The following attestation bundles were made for ssh_socks_cli-0.1.0.tar.gz:
Publisher:
publish.yml on sergioarojasm98/ssh-socks-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ssh_socks_cli-0.1.0.tar.gz -
Subject digest:
6ec85b50536ba8efeed472cd10dbe82ceae4c8417bc0aa1e8d6b28e0c0e8d8e8 - Sigstore transparency entry: 1280522054
- Sigstore integration time:
-
Permalink:
sergioarojasm98/ssh-socks-cli@8cdc67fc50c9c70f09ffe8036c21aa1698e9c3a2 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/sergioarojasm98
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8cdc67fc50c9c70f09ffe8036c21aa1698e9c3a2 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ssh_socks_cli-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ssh_socks_cli-0.1.0-py3-none-any.whl
- Upload date:
- Size: 24.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc446da53435451b723f00d92c8cd971268f93b8cc31ea5422b65dd265839b32
|
|
| MD5 |
ff983a1bd5780e2f8c17d6856877f9ef
|
|
| BLAKE2b-256 |
709c5fe10fa93e56b525145a8fa6f45a105e07add34992cacd1f01ab26b0a990
|
Provenance
The following attestation bundles were made for ssh_socks_cli-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on sergioarojasm98/ssh-socks-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ssh_socks_cli-0.1.0-py3-none-any.whl -
Subject digest:
cc446da53435451b723f00d92c8cd971268f93b8cc31ea5422b65dd265839b32 - Sigstore transparency entry: 1280522058
- Sigstore integration time:
-
Permalink:
sergioarojasm98/ssh-socks-cli@8cdc67fc50c9c70f09ffe8036c21aa1698e9c3a2 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/sergioarojasm98
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8cdc67fc50c9c70f09ffe8036c21aa1698e9c3a2 -
Trigger Event:
release
-
Statement type: