SSH Tunnel
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 fi
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 fi
Real data. Real impact.
Emerging
Developers
Per week
Open source
Skills give you superpowers. Install in 30 seconds.
SSH tunneling, port forwarding, and secure remote access. Covers local/remote/dynamic forwards, jump hosts, ProxyCommand, multiplexing, key management, and connection debugging.
# Forward local port 5432 to remote's localhost:5432 # Use case: access a remote PostgreSQL database as if it were local ssh -L 5432:localhost:5432 user@remote-serverThen connect locally:
psql -h localhost -p 5432 -U dbuser mydb
Forward to a different host accessible from the remote
Remote server can reach db.internal:5432, but you can't
ssh -L 5432:db.internal:5432 user@remote-server
Forward multiple ports
ssh -L 5432:db.internal:5432 -L 6379:redis.internal:6379 user@remote-server
Run in background (no shell)
ssh -fNL 5432:db.internal:5432 user@remote-server
-f = background after auth
-N = no remote command
-L = local forward
# Make your local port 3000 accessible on the remote server's port 8080 ssh -R 8080:localhost:3000 user@remote-server # On the remote: curl http://localhost:8080 → hits your local :3000Expose to all interfaces on the remote (not just localhost)
Requires GatewayPorts yes in remote sshd_config
ssh -R 0.0.0.0:8080:localhost:3000 user@remote-server
Background mode
ssh -fNR 8080:localhost:3000 user@remote-server
# Create a SOCKS5 proxy on local port 1080 ssh -D 1080 user@remote-serverRoute browser traffic through the tunnel
Configure browser proxy: SOCKS5, localhost:1080
Use with curl
curl --socks5-hostname localhost:1080 https://example.com
Background mode
ssh -fND 1080 user@remote-server
# Connect through a bastion host ssh -J bastion-user@bastion.example.com target-user@internal-serverChain multiple jumps
ssh -J bastion1,bastion2 target-user@internal-server
With port forward through bastion
ssh -J bastion-user@bastion -L 5432:db.internal:5432 target-user@app-server
# Equivalent to ProxyJump but works on older OpenSSH ssh -o ProxyCommand="ssh -W %h:%p bastion-user@bastion" target-user@internal-server
# ~/.ssh/configBastion 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/configGlobal 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
# ~/.ssh/config Host * ControlMaster auto ControlPath ~/.ssh/sockets/%r@%h-%p ControlPersist 600First connection opens socket, subsequent connections reuse it
Much faster for repeated ssh/scp/rsync to same host
# Create socket directory mkdir -p ~/.ssh/sockets # Manually manage control socket ssh -O check prod # Check if connection is alive ssh -O stop prod # Close the master connection ssh -O exit prod # Close immediately
# Ed25519 (recommended — fast, secure, short keys) ssh-keygen -t ed25519 -C "user@machine" -f ~/.ssh/mykey_ed25519RSA 4096 (wider compatibility)
ssh-keygen -t rsa -b 4096 -C "user@machine" -f ~/.ssh/mykey_rsa
Generate without passphrase (for automation only)
ssh-keygen -t ed25519 -N "" -f ~/.ssh/deploy_key
# Copy public key to remote server ssh-copy-id -i ~/.ssh/mykey_ed25519.pub user@remote-serverManual (if ssh-copy-id unavailable)
cat ~/.ssh/mykey_ed25519.pub | ssh user@remote-server "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
# Start agent (usually auto-started) eval "$(ssh-agent -s)"Add key to agent
ssh-add ~/.ssh/mykey_ed25519
Add with expiry (key removed after timeout)
ssh-add -t 3600 ~/.ssh/mykey_ed25519
List loaded keys
ssh-add -l
Remove all keys
ssh-add -D
Agent forwarding (use your local keys on remote hosts)
ssh -A user@remote-server
On remote: ssh git@github.com → uses your local key
SECURITY: only forward to trusted hosts
# SSH is strict about permissions. Fix common issues: chmod 700 ~/.ssh chmod 600 ~/.ssh/id_ed25519 # Private key chmod 644 ~/.ssh/id_ed25519.pub # Public key chmod 600 ~/.ssh/config chmod 600 ~/.ssh/authorized_keys
# Copy file to remote scp file.txt user@remote:/path/to/destination/Copy from remote
scp user@remote:/path/to/file.txt ./local/
Copy directory recursively
scp -r ./local-dir user@remote:/path/to/
Through jump host
scp -o ProxyJump=bastion file.txt user@internal:/path/
With specific key and port
scp -i ~/.ssh/mykey -P 2222 file.txt user@remote:/path/
# Sync directory (only changed files) rsync -avz ./local-dir/ user@remote:/path/to/remote-dir/Dry run (preview changes)
rsync -avzn ./local-dir/ user@remote:/path/to/remote-dir/
Delete files on remote that don't exist locally
rsync -avz --delete ./local-dir/ user@remote:/path/to/remote-dir/
Exclude patterns
rsync -avz --exclude='node_modules' --exclude='.git' ./project/ user@remote:/deploy/
With specific SSH options
rsync -avz -e "ssh -i ~/.ssh/deploy_key -p 2222" ./dist/ user@remote:/var/www/
Resume interrupted transfer
rsync -avz --partial --progress large-file.tar.gz user@remote:/path/
Through jump host
rsync -avz -e "ssh -J bastion" ./files/ user@internal:/path/
# Increasing verbosity levels ssh -v user@remote # Basic debug ssh -vv user@remote # More detail ssh -vvv user@remote # Maximum detailCommon issues visible in verbose output:
"Connection refused" → SSH server not running or wrong port
"Connection timed out" → Firewall blocking, wrong IP
"Permission denied (publickey)" → Key not accepted
"Host key verification failed" → Server fingerprint changed
# Check if SSH port is open nc -zv remote-host 22 # or ssh -o ConnectTimeout=5 -o BatchMode=yes user@remote echo okCheck which key the server accepts
ssh -o PreferredAuthentications=publickey -v user@remote 2>&1 | grep "Offering|Accepted"
Test config without connecting
ssh -G remote-host # Print effective config for this host
# "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED" # Server was reinstalled / IP reassigned ssh-keygen -R remote-host # Remove old fingerprint ssh user@remote-host # Accept new fingerprint"Too many authentication failures"
SSH agent is offering too many keys
ssh -o IdentitiesOnly=yes -i ~/.ssh/specific_key user@remote
"Connection closed by remote host"
Often: MaxSessions or MaxStartups limit on server
Or: fail2ban banned your IP
Tunnel keeps dying
Add keepalive in config or command line:
ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=5 user@remote
Permission denied despite correct key
Check remote: /var/log/auth.log or /var/log/secure
Common: wrong permissions on ~/.ssh or authorized_keys
# 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)
~/.ssh/config for everything. Named hosts with stored settings are faster and less error-prone than typing long commands.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.-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.~/.ssh/config organized with comments. Future-you will appreciate it.~. escape sequence is the only way to kill a stuck SSH session without closing the terminal.No automatic installation available. Please visit the source repository for installation instructions.
View Installation Instructions1,500+ AI skills, agents & workflows. Install in 30 seconds. Part of the Torly.ai family.
© 2026 Torly.ai. All rights reserved.