Skip to main content

SSH Tunnel Switch

Rotate between SSH SOCKS5 tunnels using a REST API or CLI. A built-in HTTP proxy forwards HTTP requests and HTTPS CONNECT traffic through the active tunnel, with remote DNS resolution through SOCKS5.

The server runs independently. It requires Python 3.12+ and OpenSSH; no OpenCode, Node.js, Bun, or plugin is needed.

Install and run

Install from a checkout until the first PyPI release is available:

python3 -m venv .venv
.venv/bin/python -m pip install .
cp examples/config.yaml config.yaml

After publication, install with python -m pip install ssh-tunnel-switch. The executable remains tunnel-switch, and Python imports use tunnel_switch.

Edit config.yaml with your SSH host aliases and the SOCKS, HTTP proxy and API bind IP/port. Only host is required for each SSH server. name defaults to host; user, port and identity_file are optional overrides. When omitted (or null), the SSH command leaves them unset, so the system SSH client inherits settings from ~/.ssh/config for the account running the service. Without configured SSH values, OpenSSH uses its own defaults. Identity paths relative to the YAML file resolve from its directory; ~ expands. YAML is mandatory for every CLI command. Unknown fields, invalid IPs/ports, duplicate server names and missing identity files are rejected.

A host-only server list can use existing SSH config aliases:

servers:
  - host: ssh-one
  - host: ssh-two

Normal HostName, User, Port, IdentityFile, ProxyJump and other SSH config settings apply. An explicit YAML user, port or identity_file is passed as -l, -p or -i. Existing configurations with named servers and explicit values still work. Effective server names must remain unique, including names derived from host aliases.

Authenticate to each SSH server once using your normal SSH client and verify its host key. The service requires key/agent authentication (BatchMode=yes) and known hosts (StrictHostKeyChecking=yes). It does not prompt for passwords or accept unknown host keys. The SSH accounts must permit TCP forwarding.

The API token lives in api.token in the YAML file. On startup, serve generates and saves a random token if the field is missing, null or blank. Existing tokens are reused. In both cases it prints API token: ... to the console before starting the API. Generated-token writes are atomic, preserve YAML comments, and save the file with mode 0600. If saving fails, startup fails rather than using an unsaved token.

.venv/bin/tunnel-switch validate --config config.yaml
.venv/bin/tunnel-switch serve --config config.yaml

No token environment variable is used. status and rotate read the saved YAML token; they do not generate one. serve starts the first tunnel unless start_on_launch: false. Ctrl-C cleans up its SSH process. Each command supports --help; command/configuration/network failures print an error and CLI help with a nonzero exit code.

Run exactly one service process for a given SOCKS socket. The supplied CLI uses one Uvicorn worker, without reload. It manages only SSH processes it spawned. An existing process occupying the SOCKS address is rejected rather than terminated. The cooldown also applies after startup and failed rotations, so an immediate rotation within the default 30 seconds returns 429.

Use the proxy with any client

Start the service, then configure an HTTP proxy-aware client:

curl --proxy http://127.0.0.1:8118 https://example.com/

Alternatively, set HTTP_PROXY and HTTPS_PROXY to http://127.0.0.1:8118 for clients that support those variables. Keep local control requests direct using NO_PROXY=localhost,127.0.0.1,::1 or curl --noproxy '*'. SOCKS5-aware clients can use socks5h://127.0.0.1:1080 directly.

The HTTP proxy defaults to enabled on loopback port 8118. It streams HTTP/1.1 requests and supports HTTPS CONNECT without decrypting TLS. Forwarding fails when SOCKS is unavailable; it never falls back to a direct destination connection. Rotation closes existing tunneled connections. The API remains responsive during rotation. There is no automatic SSH restart loop.

The HTTP and SOCKS listeners are unauthenticated. Keep them on loopback or restrict access with a firewall. The bearer token protects only the REST API. Use a TLS reverse proxy for remote API access. See security.

REST API and CLI

All endpoints require Authorization: Bearer <token>. FastAPI interactive API docs are available at /docs.

Endpoint Result
GET /status State, connection ID, start time, server, SOCKS and HTTP proxy addresses, and last error
POST /rotate Stops current tunnel, starts next server, returns status after readiness

Overlapping rotations and cooldown violations return 429, with Retry-After; callers must trigger again later. They are not queued. Startup/SSH failures return 503 and leave the service disconnected; another trigger after the cooldown advances to the next server. A single-server list restarts the same server. Status remains responsive during rotation and returns state: "rotating"; connection ID/start time are null until the new tunnel is ready. After SSH exits, status reports disconnected and clears active connection fields. There is no automatic restart loop.

.venv/bin/tunnel-switch status --config config.yaml
.venv/bin/tunnel-switch rotate --config config.yaml
curl --noproxy '*' -H "Authorization: Bearer <token-from-YAML>" http://127.0.0.1:8765/status
curl --noproxy '*' -X POST -H "Authorization: Bearer <token-from-YAML>" http://127.0.0.1:8765/rotate

Example connected status:

{
  "state": "connected",
  "connection_id": "9d7e9746-c4e2-4a43-8da5-674ab452cb91",
  "started_at": "2026-10-14T12:00:00+00:00",
  "server": "first",
  "proxy": {"ip": "127.0.0.1", "port": 1080},
  "http_proxy": {"enabled": true, "ip": "127.0.0.1", "port": 8118},
  "last_error": null
}

Development and deployment

License

MIT.

Metadata

Release files for ssh-tunnel-switch 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ssh-tunnel-switch 0.1.0
File Size Uploaded
ssh_tunnel_switch-0.1.0.tar.gz 21.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ssh-tunnel-switch 0.1.0
File Interpreter ABI Platform
ssh_tunnel_switch-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 35.9 kB

Release files / ssh_tunnel_switch-0.1.0.tar.gz

Download URL ssh_tunnel_switch-0.1.0.tar.gz
Size 21.6 kB
Tags Source
SHA-256 checksum
How to use checksums
049b884f67c2f87a49d9f4d276dd44c17d1414477dbc3a08a90bf61fb1d4df78
BLAKE2b-256 checksum
How to use checksums
fdf8015aa514ee4c8e7da0a9385d70e2d5d3c56f390b5ddd915a618bc5a61663
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / ssh_tunnel_switch-0.1.0-py3-none-any.whl

Download URL ssh_tunnel_switch-0.1.0-py3-none-any.whl
Size 14.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c78312eed1f50bf441c1e944a717d1f1ea4902a6a4fe71b7c82a909e2ac0010a
BLAKE2b-256 checksum
How to use checksums
d2bd49f9e429cd1f06ac68c90c76f3bdf216a6a75c5e9f5a6261046256060f8e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release history Release notifications | RSS feed

1.0.0

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page