| name | ssh-tunnel |
| description | SSH tunneling, port forwarding, and remote access patterns. Use when setting up local/remote/dynamic port forwards, configuring jump hosts, managing SSH keys, multiplexing connections, transferring files with scp/rsync, or debugging SSH connection issues. |
| metadata | {"clawdbot":{"emoji":"🔑","requires":{"bins":["ssh"]},"os":["linux","darwin","win32"]}} |
SSH Tunnel
SSH tunneling, port forwarding, and secure remote access. Covers local/remote/dynamic forwards, jump hosts, ProxyCommand, multiplexing, key management, and connection debugging.
When to Use
- Accessing a remote database through a firewall (local port forward)
- Exposing a local dev server to a remote machine (remote port forward)
- Using a remote server as a SOCKS proxy (dynamic forward)
- Connecting through bastion/jump hosts
- Managing SSH keys and agent forwarding
- Transferring files securely (scp, rsync)
- Debugging SSH connection failures
Port Forwarding
Local forward (access remote service locally)
ssh -L 5432:localhost:5432 user@remote-server
psql -h localhost -p 5432 -U dbuser mydb
ssh -L 5432:db.internal:5432 user@remote-server
ssh -L 5432:db.internal:5432 -L 6379:redis.internal:6379 user@remote-server
ssh -fNL 5432:db.internal:5432 user@remote-server
Remote forward (expose local service remotely)
ssh -R 8080:localhost:3000 user@remote-server
ssh -R 0.0.0.0:8080:localhost:3000 user@remote-server
ssh -fNR 8080:localhost:3000 user@remote-server
Dynamic forward (SOCKS proxy)
ssh -D 1080 user@remote-server
curl --socks5-hostname localhost:1080 https://example.com
ssh -fND 1080 user@remote-server
Jump Hosts / Bastion
ProxyJump (simplest, OpenSSH 7.3+)
ssh -J bastion-user@bastion.example.com target-user@internal-server
ssh -J bastion1,bastion2 target-user@internal-server
ssh -J bastion-user@bastion -L 5432:db.internal:5432 target-user@app-server
ProxyCommand (older systems, more flexible)
ssh -o ProxyCommand="ssh -W %h:%p bastion-user@bastion" target-user@internal-server
SSH Config for jump hosts
# ~/.ssh/config
# Bastion host
Host bastion
HostName bastion.example.com
User bastion-user
IdentityFile ~/.ssh/bastion_key
# Internal servers (automatically use bastion)
Host app-server
HostName 10.0.1.50
User deploy
ProxyJump bastion
Host db-server
HostName 10.0.2.30
User admin
ProxyJump bastion
LocalForward 5432 localhost:5432
# Now just: ssh app-server
# Or: ssh db-server (auto-forwards port 5432)
SSH Config Patterns
Essential config
# ~/.ssh/config
# Global defaults
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes
IdentitiesOnly yes
# Named hosts
Host prod
HostName 203.0.113.50
User deploy
IdentityFile ~/.ssh/prod_ed25519
Port 2222
Host staging
HostName staging.example.com
User deploy
IdentityFile ~/.ssh/staging_ed25519
# Wildcard patterns
Host *.dev.example.com
User developer
IdentityFile ~/.ssh/dev_key
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
Connection multiplexing (reuse connections)
# ~/.ssh/config
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
# First connection opens socket, subsequent connections reuse it
# Much faster for repeated ssh/scp/rsync to same host
mkdir -p ~/.ssh/sockets
ssh -O check prod
ssh -O stop prod
ssh -O exit prod
Key Management
Generate keys
ssh-keygen -t ed25519 -C "user@machine" -f ~/.ssh/mykey_ed25519
ssh-keygen -t rsa -b 4096 -C "user@machine" -f ~/.ssh/mykey_rsa
ssh-keygen -t ed25519 -N "" -f ~/.ssh/deploy_key
Deploy keys
ssh-copy-id -i ~/.ssh/mykey_ed25519.pub user@remote-server
cat ~/.ssh/mykey_ed25519.pub | ssh user@remote-server "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
SSH Agent
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/mykey_ed25519
ssh-add -t 3600 ~/.ssh/mykey_ed25519
ssh-add -l
ssh-add -D
ssh -A user@remote-server
File permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
chmod 600 ~/.ssh/config
chmod 600 ~/.ssh/authorized_keys
File Transfer
scp
scp file.txt user@remote:/path/to/destination/
scp user@remote:/path/to/file.txt ./local/
scp -r ./local-dir user@remote:/path/to/
scp -o ProxyJump=bastion file.txt user@internal:/path/
scp -i ~/.ssh/mykey -P 2222 file.txt user@remote:/path/
rsync over SSH
rsync -avz ./local-dir/ user@remote:/path/to/remote-dir/
rsync -avzn ./local-dir/ user@remote:/path/to/remote-dir/
rsync -avz --delete ./local-dir/ user@remote:/path/to/remote-dir/
rsync -avz --exclude='node_modules' --exclude='.git' ./project/ user@remote:/deploy/
rsync -avz -e "ssh -i ~/.ssh/deploy_key -p 2222" ./dist/ user@remote:/var/www/
rsync -avz --partial --progress large-file.tar.gz user@remote:/path/
rsync -avz -e "ssh -J bastion" ./files/ user@internal:/path/
Connection Debugging
Verbose output
ssh -v user@remote
ssh -vv user@remote
ssh -vvv user@remote
Test connectivity
nc -zv remote-host 22
ssh -o ConnectTimeout=5 -o BatchMode=yes user@remote echo ok
ssh -o PreferredAuthentications=publickey -v user@remote 2>&1 | grep "Offering\|Accepted"
ssh -G remote-host
Common fixes
ssh-keygen -R remote-host
ssh user@remote-host
ssh -o IdentitiesOnly=yes -i ~/.ssh/specific_key user@remote
ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=5 user@remote
Kill stuck SSH sessions
# If SSH session hangs (frozen terminal):
# Type these characters in sequence:
~. # Disconnect
~? # Show escape commands
~# # List forwarded connections
~& # Background SSH (when waiting for tunnel to close)
# The ~ must be the first character on a new line (press Enter first)
Tips
- Use
~/.ssh/config for everything. Named hosts with stored settings are faster and less error-prone than typing long commands.
- Ed25519 keys are preferred over RSA. They're shorter, faster, and equally secure.
- Connection multiplexing (
ControlMaster) makes repeated connections instant. Enable it globally.
rsync is almost always better than scp for anything beyond a single file. It handles interruptions, only transfers changes, and supports compression.
- Agent forwarding (
-A) is convenient but a security risk on untrusted servers. The remote host can use your agent to authenticate as you. Prefer ProxyJump instead.
ServerAliveInterval 60 in config prevents most "broken pipe" disconnections.
- Keep your
~/.ssh/config organized with comments. Future-you will appreciate it.
- The
~. escape sequence is the only way to kill a stuck SSH session without closing the terminal.