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 strictknown_hostschecks 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:
startattaches to the terminal,start -Ddetaches;stop/restart/statustalk 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.tomlscaffolds one commented-out[[channels]]block per alias in your SSH config — uncomment and fill in ports. Orcp config.example.toml config.tomlfor 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" # required, this machine's side
remote = "port" | "host:port" # required, the SSH server's side
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 onlocal; the server dialsremotefor each connection.remote->local(≈ssh -R): the server bindsremote; incoming traffic is bridged tolocalon 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"[::1]:3306"→ IPv6
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.
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 = "192.168.1.50:3000" # bare "3000" means 127.0.0.1:3000
remote = "8080" # edge-server binds 127.0.0.1:8080
Explanation: Connections to 127.0.0.1:8080 on edge-server are forwarded to 192.168.1.50:3000. To accept external connections on the server, set remote = "0.0.0.0:8080" 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
- Troubleshooting — common connection, host-key, and port issues.
- Configuration reference — every field, every edge case.
- How to use — task-oriented walkthroughs.
- Connection testing — verify configured channels.
- Architecture — how channels, sessions, and reconnection fit together.
License
MIT — see LICENSE.
Metadata
Release files for ssh-channels-hub 0.5.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ssh_channels_hub-0.5.3-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details for ssh_channels_hub-0.5.3-py3-none-win_amd64.whl |
| ssh_channels_hub-0.5.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details for ssh_channels_hub-0.5.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
| ssh_channels_hub-0.5.3-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details for ssh_channels_hub-0.5.3-py3-none-macosx_11_0_arm64.whl |
Total release size: 7.6 MB
Release files / ssh_channels_hub-0.5.3-py3-none-win_amd64.whl
| Download URL | ssh_channels_hub-0.5.3-py3-none-win_amd64.whl |
|---|---|
| Size | 2.5 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
bdf5d8904af0acfc6197497401424cf4532a4c6898f9fb5699164e75c0922e10
|
|
BLAKE2b-256 checksum How to use checksums |
33262244870392847b452591600bb31fbbc6b24c6aec00ac8d5309047b5953fc
|
| 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 Aug 19, 2026.
Transparency logRelease files / ssh_channels_hub-0.5.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | ssh_channels_hub-0.5.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 2.6 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
cd5b08d8857665b865d7b2a410571ad665d9a21fef2b54a395f283ddcc1077a4
|
|
BLAKE2b-256 checksum How to use checksums |
a98e938b34f9690c1e841e7f2c9084fce8f9f0c377c513fcadbd1c73d1d0acf2
|
| 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 Aug 19, 2026.
Transparency logRelease files / ssh_channels_hub-0.5.3-py3-none-macosx_11_0_arm64.whl
| Download URL | ssh_channels_hub-0.5.3-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 2.4 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
d869b2ec90a32659cab943328674c0e06ab2c560432443e6d839e4adc567a179
|
|
BLAKE2b-256 checksum How to use checksums |
4d6d64f93ecfe6295085ad1b37b4cea9af5749ef8459156175b5c295c4400a4c
|
| 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 Aug 19, 2026.
Transparency logSecurity for ssh-channels-hub 0.5.3
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.
Attestation for ssh_channels_hub-0.5.3-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 Aug 19, 2026.
Transparency logA 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.