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— alias-only references, publickey auth, 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.
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 install:
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 | ~/.config/ssh-channels-hub/config.toml |
| 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
Credentials
~/.ssh/config can't hold passwords or key passphrases. When SSH config alone can't authenticate the host, add an [auth.<alias>] block keyed by the SSH config 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 # 0 = unlimited
initial_delay_secs = 1
max_delay_secs = 30
use_exponential_backoff = true
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
Listen on every interface so other LAN machines can use the tunnel (mind your firewall):
[[channels]]
name = "shared-db"
hostname = "db-server"
direction = "local->remote"
local = "0.0.0.0:3306"
remote = "3306"
Expose a local service to the SSH server (ssh -R):
[[channels]]
name = "expose-local-web"
hostname = "jumpbox"
direction = "remote->local"
remote = "8022" # server binds 127.0.0.1:8022
local = "80" # incoming traffic bridges to 127.0.0.1:80 here
(For the server to bind 0.0.0.0:8022, set remote = "0.0.0.0:8022" and enable GatewayPorts in the server's 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 re-start as 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.
Troubleshooting
Channel '...' references host alias '...', but no Host ... block exists— typo inhostname, or the alias is missing from~/.ssh/config.Address(es) already in use— something else is bound to yourlocaladdress. Change the port or stop the other process. Find the culprit withlsof -i :PORT(Linux/macOS) ornetstat -ano | findstr :PORT(Windows).- Bind ports < 1024 — needs root (Linux/macOS) or Administrator (Windows).
- Connection fails —
ssh <alias>manually first to isolate SSH config / network / key permission issues. - Encrypted key not unlocking — set
[auth.<alias>] passphrase = "...". - Full debug output —
ssh-channels-hub start --debuglogs each channel's SSH handshake, channel open, and reconnection attempts.
Further reading
- Configuration reference — every field, every edge case.
- How to use — task-oriented walkthroughs.
- Architecture — how channels, sessions, and reconnection fit together.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
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_channels_hub-0.4.5-py3-none-win_amd64.whl.
File metadata
- Download URL: ssh_channels_hub-0.4.5-py3-none-win_amd64.whl
- Upload date:
- Size: 2.5 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74c189bae1dc804b3275711c23610ea06934209009c2a918efb72dae8d486ab2
|
|
| MD5 |
cff458c9b6e55687866adcc2df1687df
|
|
| BLAKE2b-256 |
b0664d3dc5c2c3c846c16cadcdfc937978d05285b18cc2cee81a6d9b5dc4e0a2
|
Provenance
The following attestation bundles were made for ssh_channels_hub-0.4.5-py3-none-win_amd64.whl:
Publisher:
build.yml on maoXyzt/ssh-channels-hub
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ssh_channels_hub-0.4.5-py3-none-win_amd64.whl -
Subject digest:
74c189bae1dc804b3275711c23610ea06934209009c2a918efb72dae8d486ab2 - Sigstore transparency entry: 2343296818
- Sigstore integration time:
-
Permalink:
maoXyzt/ssh-channels-hub@137a42ab25df8f856fd8b916604b22fedea12c87 -
Branch / Tag:
refs/tags/v0.4.5 - Owner: https://github.com/maoXyzt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@137a42ab25df8f856fd8b916604b22fedea12c87 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ssh_channels_hub-0.4.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: ssh_channels_hub-0.4.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 2.6 MB
- Tags: Python 3, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
272b7585b3e194a6ba309be854a13d01299744ae68883cca8adf7de4e046703f
|
|
| MD5 |
ba4cc7962cf7e0c70522b172219cddb7
|
|
| BLAKE2b-256 |
f0f89bd34b038abe5a78a002c96cd40e1ce5e7cf7aedeba3b9bda4760729dd57
|
Provenance
The following attestation bundles were made for ssh_channels_hub-0.4.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
build.yml on maoXyzt/ssh-channels-hub
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ssh_channels_hub-0.4.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
272b7585b3e194a6ba309be854a13d01299744ae68883cca8adf7de4e046703f - Sigstore transparency entry: 2343296596
- Sigstore integration time:
-
Permalink:
maoXyzt/ssh-channels-hub@137a42ab25df8f856fd8b916604b22fedea12c87 -
Branch / Tag:
refs/tags/v0.4.5 - Owner: https://github.com/maoXyzt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@137a42ab25df8f856fd8b916604b22fedea12c87 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ssh_channels_hub-0.4.5-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: ssh_channels_hub-0.4.5-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.4 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c12a75d73e8e051ae4caa64ca0161623fbbaf317577fd3f3efef9ec491ae3a4d
|
|
| MD5 |
5604fc2469b183fe813549d81ffb1482
|
|
| BLAKE2b-256 |
e1e75d39e28dfa417f8ecd1c62c4551aef7b238d96d24e1adaac95f78735c417
|
Provenance
The following attestation bundles were made for ssh_channels_hub-0.4.5-py3-none-macosx_11_0_arm64.whl:
Publisher:
build.yml on maoXyzt/ssh-channels-hub
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ssh_channels_hub-0.4.5-py3-none-macosx_11_0_arm64.whl -
Subject digest:
c12a75d73e8e051ae4caa64ca0161623fbbaf317577fd3f3efef9ec491ae3a4d - Sigstore transparency entry: 2343296689
- Sigstore integration time:
-
Permalink:
maoXyzt/ssh-channels-hub@137a42ab25df8f856fd8b916604b22fedea12c87 -
Branch / Tag:
refs/tags/v0.4.5 - Owner: https://github.com/maoXyzt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@137a42ab25df8f856fd8b916604b22fedea12c87 -
Trigger Event:
push
-
Statement type: