Skip to main content

SSH Channels Hub

English | 中文

Declarative SSH tunnels with auto-reconnect. Define your port forwards once in TOML, start one service, and they all come up — reconnecting automatically when the link drops.

Cross-platform (Linux, macOS, Windows). Written in Rust on top of russh.

Why

Reach for this when ssh -L 3306:127.0.0.1:3306 db.example.com has grown into "I have five of those, my laptop sleeps, my Wi-Fi flakes, and I want them all back when I open the lid."

  • Declarative: tunnels live in config.toml, not in shell history or terminal panes.
  • No host config duplication: host info (HostName / User / Port / IdentityFile) is read straight from ~/.ssh/config — you reference aliases.
  • ProxyJump aware: chain through bastions defined in ~/.ssh/config — using alias-only references, public-key authentication, and strict known_hosts checks for targets and jumps. See docs/configuration.md §3.4.
  • Auto-reconnect: compatible tunnels share one SSH session; a dropped route reconnects with jittered backoff without disturbing other routes.
  • Both directions in one schema: local-to-remote (ssh -L) and remote-to-local (ssh -R).
  • Foreground or daemon: start attaches to the terminal, start -D detaches; stop / restart / status talk to the running process via IPC.

Quickstart

1. Run or install

Run directly with uvx (recommended, no installation required):

uvx ssh-channels-hub --help

Or install it with pip inside an activated virtual environment:

pip install ssh-channels-hub
ssh-channels-hub --help

The wheel installs the same ssh-channels-hub binary on Linux x86_64, macOS ARM64, and Windows x86_64; it does not run through Python.

If you already have a Rust/Cargo toolchain, you can also install with:

cargo binstall ssh-channels-hub          # requires cargo-binstall; installs a prebuilt binary
cargo install ssh-channels-hub --locked # build and install from source

For development, clone and build the source:

git clone https://github.com/maoXyzt/ssh-channels-hub.git
cd ssh-channels-hub
cargo build --release           # binary at target/release/ssh-channels-hub (or .exe on Windows)

2. Have the host in ~/.ssh/config

Host my-db
  HostName db.example.com
  User myuser
  IdentityFile ~/.ssh/id_rsa

3. Write config.toml in the current directory:

[[channels]]
name      = "db"
hostname  = "my-db"             # alias from ~/.ssh/config
direction = "local->remote"     # ssh -L
local     = "3306"              # listen on 127.0.0.1:3306
remote    = "3306"              # server connects to 127.0.0.1:3306

4. Run

uvx ssh-channels-hub start      # no installation
# or, after pip or Cargo installation:
ssh-channels-hub start          # Ctrl+C to stop
# or, after cargo build:
./target/release/ssh-channels-hub start       # Linux/macOS
.\target\release\ssh-channels-hub.exe start  # Windows PowerShell

Now mysql -h 127.0.0.1 -P 3306 goes through the tunnel.

Tip: ssh-channels-hub generate -o config.toml scaffolds one commented-out [[channels]] block per alias in your SSH config — uncomment and fill in ports. Or cp config.example.toml config.toml for an annotated template.

Configuration

config.toml is looked up in this order (first existing wins):

Platform Path
Current directory (always tried first) ./config.toml
Linux / macOS $XDG_CONFIG_HOME/ssh-channels-hub/config.toml (~/.config/... when unset)
Windows %APPDATA%\ssh-channels-hub\config.toml

--config /path/to/file overrides the lookup.

Channel schema

[[channels]]
name      = "string"                            # required, unique identifier
hostname  = "ssh-config-alias"                  # required; resolves via ~/.ssh/config
direction = "local->remote" | "remote->local"   # required
local     = "port" | "host:port" | "URL"        # required, this machine's side
remote    = "port" | "host:port" | "URL"        # required, the SSH server's side
mode      = "tcp" | "http-gateway"              # optional, default: tcp

local and remote always name the address on their respective side regardless of direction. Direction decides who listens:

  • local->remote (≈ ssh -L): this machine listens on local; the server dials remote for each connection.
  • remote->local (≈ ssh -R): the server binds remote; incoming traffic is bridged to local on this side.

Endpoints accept:

  • "3306" → 127.0.0.1:3306 (bare port, host defaults to loopback)
  • "127.0.0.1:3306" → explicit form
  • "0.0.0.0:8080" → bind on every interface
  • "db.internal:5432" → hostname and port
  • "[::1]:3306" → IPv6
  • "https://api.internal/v1" → api.internal:443 (HTTP defaults to 80)

For remote->local, this machine resolves and dials the local hostname for every incoming connection. URL paths are not part of the TCP target. In the default tcp mode, HTTPS remains end-to-end: the daemon does not terminate TLS or rewrite Host, SNI, or request paths.

Choose a forwarding mode

Need Mode
Forward TCP traffic and let the client handle TLS, including HTTPS/WSS, mTLS, or HTTP/2 Default tcp; omit mode
Let the daemon handle TLS and upstream Host/SNI for one fixed HTTPS/WSS service http-gateway; clients use HTTP/WS

TCP mode carries HTTPS/WSS end to end; gateway mode accepts HTTP/WS and uses HTTPS/WSS to the upstream. An https:// endpoint alone does not enable gateway mode; in TCP mode it only supplies the target host and port.

For gateway mode, add a reverse channel:

[[channels]]
name      = "https-gateway"
hostname  = "edge-server"
direction = "remote->local"
remote    = "5443"
local     = "https://api.internal.example"
mode      = "http-gateway"

On the SSH server, use curl http://127.0.0.1:5443/v1 or connect a WebSocket client to ws://127.0.0.1:5443/<actual-websocket-path>. Both reach the same TLS upstream. local = "api.internal.example:443" is equivalent to the HTTPS URL.

Gateway listeners must be loopback, and the SSH server must use GatewayPorts no or clientspecified. The client-to-loopback leg is plaintext; SSH encrypts traffic between the server and daemon.

See the step-by-step examples or configuration semantics and limits.

Web status page

The Web status page is enabled by default, and its loopback URL is printed at startup. It shows channel endpoints, health, retries, latest errors, and host-key remediation commands. HTTP Gateway channels include a mode badge, the remote HTTP listener, and the local HTTPS upstream; Open upstream opens the upstream using HTTPS. Health describes the SSH channel, not upstream HTTP availability.

Open local opens the channel's local endpoint. Set [web].enabled = false to disable the page; see the configuration reference for other options.

Credentials

~/.ssh/config can't hold passwords or key passphrases. If SSH config alone can't authenticate to a host, add an [auth.<alias>] block keyed by its alias:

[auth.my-db]
password   = "..."          # for password-auth hosts (no IdentityFile in SSH config)
# or
passphrase = "..."          # for encrypted IdentityFile

password overrides any IdentityFile. Hosts that authenticate cleanly via SSH config alone don't need an [auth.*] block at all.

Reconnection (global)

[reconnection]
# max_retries             = 0     # default: 0 = unlimited
# initial_delay_secs      = 1     # default: 1 second
# max_delay_secs          = 30    # default: 30 seconds
# use_exponential_backoff = true  # default: true

Omit this section to use all defaults.

Each retry delay includes jitter. After a finite retry cycle is exhausted, automatic recovery continues with a second exponential backoff capped at 60 seconds; a successful session resets both counters. SSH handshakes are serialized to avoid reconnect storms.

More examples

Share the tunnel on the local network

Scenario: Let other LAN machines use a database tunnel on this machine.

Configuration:

[[channels]]
name      = "shared-db"
hostname  = "db-server"
direction = "local->remote"
local     = "0.0.0.0:3306"
remote    = "3306"

Explanation: The service listens on 0.0.0.0:3306 and forwards connections to 127.0.0.1:3306 on db-server. Restrict access with a firewall.

Expose a local-network service to the SSH server

Scenario: Expose a service on the local network to edge-server with remote->local (ssh -R).

Configuration:

[[channels]]
name      = "lan-api"
hostname  = "edge-server"
direction = "remote->local"
local     = "https://api.internal.example/v1"
remote    = "18443" # edge-server binds 127.0.0.1:18443

Explanation: For every connection to 127.0.0.1:18443 on edge-server, this machine resolves api.internal.example and connects to port 443. Clients must include /v1 in their request URLs; the configured path is not added automatically. To accept external connections on the server, set remote = "0.0.0.0:18443" and configure GatewayPorts clientspecified in sshd_config.

Full field reference: docs/configuration.md.

Commands

Command What it does
start Run in the foreground (Ctrl+C to stop).
start -D / --daemon Spawn a detached background process.
stop Tell the running process to exit gracefully (via IPC).
restart Stop the running service, then restart it as a daemon.
status Show service state, per-channel health (Connected / Reconnecting / Failed / Stopped), PID, and endpoints. Add --watch / -w to refresh every --interval / -n seconds (default 2).
test Probe each configured local->remote listener to confirm the tunnel is alive. remote->local channels are skipped — verify those server-side.
validate Resolve every channel against ~/.ssh/config and report any problems.
generate -o config.toml Scaffold a config.toml from existing SSH config aliases.
hosts Scan SSH config aliases and show whether each host is supported. Use --format json for script-friendly output.

All commands accept --config /path/to/config.toml to point at a non-default file, and --debug for verbose logging.

Further reading

License

MIT — see LICENSE.

Metadata

Release files for ssh-channels-hub 0.6.1

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

Built distributions (wheels)

Table of built distributions (wheels) for ssh-channels-hub 0.6.1
File Interpreter ABI Platform Actions
ssh_channels_hub-0.6.1-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details for ssh_channels_hub-0.6.1-py3-none-win_amd64.whl
ssh_channels_hub-0.6.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details for ssh_channels_hub-0.6.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
ssh_channels_hub-0.6.1-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details for ssh_channels_hub-0.6.1-py3-none-macosx_11_0_arm64.whl

Total release size: 10.8 MB

Release files / ssh_channels_hub-0.6.1-py3-none-win_amd64.whl

Download URL ssh_channels_hub-0.6.1-py3-none-win_amd64.whl
Size 3.6 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
dca2b0abc53cdf3ed192124f2c4d56b26ea9a5817851fae381d2ec5387d13f2f
BLAKE2b-256 checksum
How to use checksums
fbaad7565b2a1a12c45c36b34542f9ae18b4541a1b1af9cd48bbe43eda7fbb27
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 2, 2026.

Transparency log

Release files / ssh_channels_hub-0.6.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl

Download URL ssh_channels_hub-0.6.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Size 3.7 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
f9162bc598c48e604699c5903936aaf53ce86b5440bbc15d361f01bb443325ce
BLAKE2b-256 checksum
How to use checksums
86dd7da75e08e6386644fb7b9fbd759488da4a5a77f60bf4ffe876588e07abd5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 2, 2026.

Transparency log

Release files / ssh_channels_hub-0.6.1-py3-none-macosx_11_0_arm64.whl

Download URL ssh_channels_hub-0.6.1-py3-none-macosx_11_0_arm64.whl
Size 3.4 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
d8b8da4fffd91a69df4dd411be5b8853e721c5369cca5650bd1216ff6c7ac36e
BLAKE2b-256 checksum
How to use checksums
67652e5a924325cd302c0b6a9306fc691de799e0afdd6de6d5dbeff259c3e4af
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 2, 2026.

Transparency log

Security for ssh-channels-hub 0.6.1

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.

Full provenance

All 3 files in this release have been uploaded with provenance metadata.

Select from 3 files

Attestation for ssh_channels_hub-0.6.1-py3-none-win_amd64.whl

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 Nov 2, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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