🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

SSH Tunnel

by @gitgoodordietrying

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.

Versionv1.0.0
Downloads4,696
Installs22
Stars⭐ 7
TERMINAL
clawhub install ssh-tunnel

πŸ“– About This Skill


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)

    # 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.
  • ⚑ When to Use

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

    πŸ“‹ Tips & Best Practices

  • 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.