ssh-tunnel-swarm
Bash tool for running a swarm of SSH tunnels — forward and reverse, many hosts at once
— off one rules file. No daemon, no config DSL beyond plain text, just ssh -L/ssh -R
looped forever per host with auto-reconnect.
Security & safety
- This opens real SSH connections using your keys to hosts you list in the rules file — anything wrong in that file connects somewhere real.
- Reverse tunnels (
reverse) expose a LOCAL service to the REMOTE host. Only pointreverserules at hosts you trust — a compromised or malicious remote can now reach whatever you bound on your side. - Forward tunnels (
forward) expose a REMOTE service through YOUR local machine. Same trust logic in reverse: don't forward into networks you don't control. - All connections force
StrictHostKeyChecking=yes— the target host must already be inknown_hostsor the connection fails. That's intentional; don't work around it by disabling host key checking. - No password auth support — private key only. Keep the key file permissions tight
(
chmod 600) and never put keys or the rules file (which references key paths) in a world-readable location or a git repo. - Every connection block runs forever in a retry loop (5s backoff) until you kill it — make sure that's actually what you want before pointing this at a host.
When to use
- Standing up several forward and/or reverse SSH tunnels across one or many hosts from
a single declarative file, instead of hand-rolling a pile of
ssh -L/ssh -Rcommands or systemd units. - Tunnels need to survive disconnects — the tool loops and reconnects automatically.
- Exposing a local dev service to a remote box (reverse), or reaching a remote-network-only service from your machine (forward).
When NOT to use
- One-off, single tunnel for a few minutes — just run
ssh -L/ssh -Rdirectly. - Password-based SSH auth — this tool only supports private key auth.
- You need a full VPN / mesh network (e.g. overlay networking, NAT traversal across many peers) — this is tunnels over plain SSH, not a VPN.
Config grammar (rules file)
Plain text file, default path rules.txt, overridable via RULES_FILE. It's a
sequence of connection blocks, separated by blank lines. Each block:
<line 1> user@host:port=/path/to/private/ssh/key
<line 2+> forward|reverse local-interface:local-port:remote-interface:remote-port
... (one or more tunnel lines)
- Header line — exactly one per block, always first:
user@hostname:port=/path/to/private/keyuser—[a-zA-Z0-9._-]+hostname— hostname or IPv4 (no scheme, no special chars)port— SSH port on the remote host,0-65535=/path/to/key— path to the private key for THIS connection; must exist on disk or the tool refuses to start (FATAL at load time).
- Tunnel lines — one or more per block, every line until the next blank line or EOF:
direction local-interface:local-port:remote-interface:remote-portdirectionis literallyforwardorreverse, nothing else.- all four of interface/port/interface/port are required, colon-separated, no spaces.
reverse→ssh -R remote-interface:remote-port:local-interface:local-port(binds on the REMOTE host, forwards back to your LOCAL interface:port).forward→ssh -L local-interface:local-port:remote-interface:remote-port(binds on your LOCAL interface, forwards to something reachable FROM the remote host).
- Blank line = end of the current block / start of the next. Multiple blocks = multiple independent SSH connections, each supervised in its own background loop.
- One host can carry any number of forward and reverse tunnel lines mixed together.
Example ruleset (forward + reverse, two hosts)
# Host 1: expose a local web app on the VPS (reverse), and reach the VPS's
# internal Postgres from your machine (forward).
deploy@vps1.example.com:22=/home/user/.ssh/deploy_vps1
reverse 0.0.0.0:8080:localhost:3000
forward localhost:15432:127.0.0.1:5432
# Host 2: reach an internal-only admin panel through a jump host (forward),
# and expose your local dev API back to that jump host (reverse).
opsuser@jump.example.com:22=/home/user/.ssh/deploy_jump
forward localhost:9090:10.0.5.20:9090
reverse 127.0.0.1:4000:localhost:4000
With the above: curl http://vps1.example.com:8080 hits your local localhost:3000;
psql -h localhost -p 15432 reaches the VPS's internal Postgres;
curl http://localhost:9090 on your machine reaches 10.0.5.20:9090 through the jump
host; and the jump host's localhost:4000 reaches your local dev API on 4000.
Running it
No subcommands, no CLI flags — everything is env vars, one invocation runs the swarm in the foreground until killed:
RULES_FILE=/path/to/rules.txt \
LOG_ENABLED=1 \
LOG_FILE=/path/to/log/file \
LOG_LEVEL=DEBUG \
ssh-tunnel-swarm
Ctrl-C (SIGINT) or SIGTERM kills every tunnel connection cleanly and exits.
Install/build details, the full env var reference, and log-level semantics are in
references/setup.md.
