v1.0.0

SSH Tunnel

gitgoodordietrying gitgoodordietrying ← All skills

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.

Downloads
1.2k
Stars
2
Versions
1
Updated
2026-02-24

Install

npx clawhub@latest install ssh-tunnel

Documentation

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)

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-server

Then 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

Remote forward (expose local service remotely)

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 :3000

Expose 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

Dynamic forward (SOCKS proxy)

Create a SOCKS5 proxy on local port 1080

ssh -D 1080 user@remote-server

Route 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

Jump Hosts / Bastion

ProxyJump (simplest, OpenSSH 7.3+)

Connect through a bastion host

ssh -J bastion-user@bastion.example.com target-user@internal-server

Chain 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

ProxyCommand (older systems, more flexible)

Equivalent to ProxyJump but works on older OpenSSH

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

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

Key Management

Generate keys

Ed25519 (recommended — fast, secure, short keys)

ssh-keygen -t ed25519 -C "user@machine" -f ~/.ssh/mykey_ed25519

RSA 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

Deploy keys

Copy public key to remote server

ssh-copy-id -i ~/.ssh/mykey_ed25519.pub user@remote-server

Manual (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"

SSH Agent

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

File permissions

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

File Transfer

scp

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/

rsync over SSH

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/

Connection Debugging

Verbose output

Increasing verbosity levels

ssh -v user@remote # Basic debug

ssh -vv user@remote # More detail

ssh -vvv user@remote # Maximum detail

Common 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

Test connectivity

Check if SSH port is open

nc -zv remote-host 22

or

ssh -o ConnectTimeout=5 -o BatchMode=yes user@remote echo ok

Check 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

Common fixes

"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

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.

Launch an agent with SSH Tunnel on Termo.