SSH Tunnel Manager
You are managing SSH tunnels using the ssh-tunnels CLI tool backed by a
declarative JSON config at ~/.ssh-tunnels/config.json.
Step 1: Ensure the tool is installed
Before doing anything, check if the CLI is available:
which ssh-tunnels 2>/dev/null && echo "installed"
If NOT installed, run:
npm install -g @statechange/ssh-tunnel-manager
This single command installs the CLI, creates ~/.ssh-tunnels/ directories,
sets up a macOS LaunchAgent for the menu bar app, and starts it automatically.
After installation, verify:
ssh-tunnels status
Step 2: Manage tunnels
Check current tunnels
ssh-tunnels status
Add a new tunnel
ssh-tunnels add \
--name "Descriptive Name" \
--host <ssh-host> \
--user <ssh-user> \
--localPort <local-port> \
--remoteHost <remote-host> \
--remotePort <remote-port> \
--enabled
Enable / disable / remove
ssh-tunnels enable <tunnel-id>
ssh-tunnels disable <tunnel-id>
ssh-tunnels remove <tunnel-id>
Sync all tunnels to match config
ssh-tunnels sync
Use --json for programmatic output
ssh-tunnels status --json
ssh-tunnels enable <id> --json
Critical knowledge
The --user flag is the #1 source of errors
The SSH username defaults to the local OS user. Most servers require root
or a specific service account. If you see "Permission denied (publickey)" in
the logs, change the user first.
The --remoteHost is relative to the SSH server
When the service runs directly on the SSH host, use --remoteHost localhost.
When the service is on a different machine accessible from the SSH host, use
that machine's internal hostname or IP.
Common ports
| Port | Service |
|---|---|
| 5432 | PostgreSQL |
| 3306 | MySQL |
| 6379 | Redis |
| 27017 | MongoDB |
| 80 | HTTP |
| 443 | HTTPS |
| 8080 | Dev server / admin UI |
| 8089 | Splunk / custom |
| 18789 | OpenClaw admin |
| 3000 | Node.js dev server |
| 9200 | Elasticsearch |
Tunnel IDs
The ID is auto-generated from the name by slugifying it (lowercase, hyphens).
For example, "Production Postgres" becomes production-postgres. Always use
the ID (not the name) for enable/disable/remove/logs commands.
After adding an enabled tunnel, verify it started
Always check status or logs after enabling a tunnel. The enable and add --enabled
commands will automatically verify and report issues, but with --json output
you should check the healthy field:
ssh-tunnels enable my-tunnel --json
# Look for: "healthy": true
If unhealthy, check logs:
ssh-tunnels logs my-tunnel
Menu bar app
The Electron menu bar app provides a GUI for the same functionality:
- Tray icon: green (all OK), yellow (partially running), red (failures)
- Click to see tunnel list with toggle switches
- "Add Tunnel..." opens a form
- "Sync Now" forces immediate reconciliation
- Auto-syncs every 30 seconds for crash recovery
The app reads/writes the same ~/.ssh-tunnels/config.json as the CLI.
Changes from either the CLI or the app are reflected in both.
The menu bar app is automatically set up as a macOS LaunchAgent during installation. To manually start it:
cd $(npm root -g)/@statechange/ssh-tunnel-manager && npx electron dist/app.mjs