Local Port Forward ssh -L
Forward a port on your local machine to a remote host through the SSH server. Use case: access a database or web service behind a firewall.
| Local Bind | Local Port | Remote Host | Remote Port | |
|---|---|---|---|---|
Command
Remote Port Forward ssh -R
Expose a local service to the remote server. Use case: let a remote server access your local dev environment, webhook testing.
| Remote Bind | Remote Port | Local Host | Local Port | |
|---|---|---|---|---|
Command
Dynamic / SOCKS Proxy ssh -D
Create a SOCKS5 proxy tunnel. All traffic routed through this proxy exits at the SSH server. Use case: bypass firewalls, route Android Studio / browser traffic through a remote server.
Command
Jump Host / Multi-Hop ssh -J
Connect to a destination through one or more intermediate jump hosts (bastion servers). Modern -J flag (OpenSSH 7.3+) or legacy ProxyCommand.
| # | Host | Port | User | |
|---|---|---|---|---|
| 1 |
Command
SSH Config ~/.ssh/config
Generate SSH config entries for ~/.ssh/config. Define hosts with forwarding rules, keep-alive settings and proxy jumps.
| Type | Bind:Port | Target:Port |
|---|
~/.ssh/config
Proxy Settings
Generate proxy configuration commands for your shell, OS settings and applications. Typically used after setting up a SOCKS tunnel with ssh -D.
Configuration Commands
Management Script
Generate a production-ready start/stop/status/restart script for managing SSH tunnels. Well-commented for beginners.
| Name | Type | SSH Host | User | Port | Forward | Key opt. | |
|---|---|---|---|---|---|---|---|
Script
Autostart Setup
Generate OS-specific autostart configuration to run your SSH tunnels at boot. Includes the management script and the service/launch config.
Output
Troubleshooting Guide
The SSH client could not connect to the server on the specified port. Common causes: SSH daemon not running, wrong port, firewall blocking.
Diagnostic Commands
Solutions
- Verify the SSH server is running:
systemctl status sshd - Check the correct port in
/etc/ssh/sshd_config(look forPort) - Check firewall rules allow incoming connections on the SSH port
- Try connecting with verbose output:
ssh -vvv user@host
The server rejected all offered authentication methods. Usually means the key is not in ~/.ssh/authorized_keys or file permissions are wrong.
Diagnostic Commands
Solutions
- Check key permissions:
chmod 600 ~/.ssh/id_ed25519andchmod 700 ~/.ssh - Verify your public key is in
~/.ssh/authorized_keyson the server - Check
/etc/ssh/sshd_configforPubkeyAuthentication yes - Check SELinux context:
restorecon -Rv ~/.ssh(RHEL/CentOS)
The local port you want to forward is already bound by another process. SSH will fail to bind with bind: Address already in use.
Diagnostic Commands
Solutions
- Kill the process occupying the port
- Choose a different local port for the tunnel
- Use
-o ExitOnForwardFailure=yesto fail cleanly instead of silently ignoring
The server's host key doesn't match the stored fingerprint in ~/.ssh/known_hosts. This can indicate a server reinstall or a man-in-the-middle attack.
Diagnostic Commands
Solutions
- If the server was reinstalled, remove the old key:
ssh-keygen -R hostname - Verify the new fingerprint with the server admin before accepting
- Warning: Do not blindly add
StrictHostKeyChecking noin production
The SSH connection was dropped unexpectedly. Common with idle tunnels, NAT timeouts, or unstable networks.
Diagnostic Commands
Solutions
- Add KeepAlive:
-o ServerAliveInterval=60 -o ServerAliveCountMax=3 - Use
TCPKeepAlive yesin SSH config - Use the Management Script generator to create auto-reconnect logic
- Check if a firewall or NAT gateway is timing out idle connections
The SSH server rejected the forwarding request. This happens when AllowTcpForwarding is disabled or the target host:port is unreachable from the server.
Diagnostic Commands
Solutions
- Check
/etc/ssh/sshd_configforAllowTcpForwarding yes - For remote forwards, check
GatewayPortssetting - Verify the target host:port is reachable from the SSH server
- Check if per-user restrictions apply in
Matchblocks
The SSH SOCKS tunnel is running but applications don't route traffic through it. Usually a configuration issue.
Diagnostic Commands
Solutions
- Verify the tunnel is running:
ss -tlnp | grep 1080 - Test with curl:
curl --socks5 127.0.0.1:1080 https://ifconfig.me - Make sure the app supports SOCKS5 (not just HTTP proxy)
- Check NO_PROXY / no_proxy env vars aren't excluding your target
- Use the Proxy Settings generator to configure applications correctly
autossh is a separate program (not part of OpenSSH) that monitors an SSH connection and automatically restarts it when it drops. It is the standard tool for persistent SSH tunnels.
Installation
How it works
autosshwraps thesshcommand and monitors the connection- When the connection drops,
autosshautomatically reconnects - Use
-M 0to disable the legacy monitoring port and rely onServerAliveInterval/ServerAliveCountMaxinstead (recommended) AUTOSSH_GATETIME=0starts monitoring immediately (useful for tunnels started at boot)
Example
autossh -M 0 -N -f -L 3306:127.0.0.1:3306 -o ServerAliveInterval=60 -o ServerAliveCountMax=3 user@server.example.com
Alternatives (no extra installation)
- while loop:
while true; do ssh -N -L ...; sleep 5; done— simple but no health checks - systemd: Use
Restart=alwaysin a service unit (see Autostart Setup generator) - Management Script: Use the Management Script generator with auto-reconnect enabled
Tip: The Local Forward, Remote Forward and Dynamic/SOCKS generators all have an Auto-Reconnect checkbox that generates ready-to-use autossh or while-loop commands.
The SSH agent is not running, not accessible, or your key is not loaded. Common when using multiple terminals, tmux/screen, cron jobs, or after reboot.
Diagnostic Commands
Solutions
- Start the agent:
eval "$(ssh-agent -s)" - Add your key:
ssh-add ~/.ssh/id_ed25519 - Persist across sessions: add
AddKeysToAgent yesto~/.ssh/config - For tmux/screen: use a fixed
SSH_AUTH_SOCKpath or symlink - macOS Keychain:
ssh-add --apple-use-keychain ~/.ssh/id_ed25519 - systemd user service:
systemctl --user enable --now ssh-agent
When using a SOCKS5 proxy, DNS queries may still leak to your local resolver instead of being resolved through the tunnel. This defeats privacy and can cause failures when accessing internal hostnames.
Diagnostic Commands
Solutions
- Use
curl --socks5-hostname(not--socks5) to resolve DNS through the proxy - Firefox: set
network.proxy.socks_remote_dns = trueinabout:config - Chrome: launch with
--proxy-server="socks5://127.0.0.1:1080" --host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE 127.0.0.1" - Use
proxychainswithproxy_dnsenabled for arbitrary applications - Key difference:
--socks5resolves DNS locally,--socks5-hostnamesends the hostname to the SOCKS server for resolution
SSH tunnels break when switching networks (Wi-Fi to mobile, VPN connect/disconnect, laptop sleep/wake). The TCP connection becomes stale but SSH may not detect it immediately.
Diagnostic Commands
Solutions
- Use aggressive keepalive:
ServerAliveInterval 15+ServerAliveCountMax 3(detects dead connection in 45s) - Use
autosshfor automatic reconnection (see Autostart generator above) - Use
moshinstead of SSH for interactive sessions (roaming-friendly, UDP-based) - systemd service with
Restart=alwaysandRestartSec=5 - Combine
TCPKeepAlive yeswithServerAliveIntervalfor dual detection - NetworkManager dispatcher: restart tunnels on network change with
/etc/NetworkManager/dispatcher.d/scripts - macOS: use
launchdwithWatchPathson network change plist events
Port Forwarding Types
| Type | Flag | Direction | Use Case | Syntax |
|---|---|---|---|---|
| Local | -L |
Local → Remote | Access remote service via local port | -L [bind:]port:host:hostport |
| Remote | -R |
Remote → Local | Expose local service to remote server | -R [bind:]port:host:hostport |
| Dynamic | -D |
SOCKS5 Proxy | Route all traffic through SSH server | -D [bind:]port |
| Jump | -J |
Multi-hop | Connect through bastion/jump hosts | -J user@jump1,user@jump2 |
Local Forward (-L)
Traffic to localhost:8080 is tunneled to db.internal:3306 via the SSH server.
Remote Forward (-R)
Traffic to server:8080 is tunneled back to your localhost:3000.
Dynamic / SOCKS (-D)
All traffic routed through the SOCKS proxy exits at the SSH server, like a lightweight VPN.
Jump Host (-J)
Multi-hop connection through one or more intermediate hosts. No agent forwarding needed with -J.
Common SSH Flags
| Flag | Description |
|---|---|
-L | Local port forwarding |
-R | Remote port forwarding |
-D | Dynamic SOCKS proxy |
-J | Jump host (ProxyJump, OpenSSH 7.3+) |
-N | Don't execute remote command (tunnel only) |
-f | Go to background after authentication |
-C | Enable compression |
-i | Identity file (private key) |
-p | SSH port (if not 22) |
-o | Set SSH option (e.g. -o ServerAliveInterval=60) |
-v / -vv / -vvv | Verbose output (more v's = more detail) |
-g | Allow remote hosts to connect to local forwarded ports |
-T | Disable pseudo-terminal allocation |
-q | Quiet mode (suppress warnings) |
Do
- Use Ed25519 or RSA 4096-bit keys
- Set
ExitOnForwardFailure yes - Bind to
127.0.0.1unless external access is needed - Use
ServerAliveIntervalto detect broken connections - Use
-J(ProxyJump) instead of agent forwarding through bastions - Set specific
AllowTcpForwardingper user in sshd_config - Use separate keys for different purposes/servers
- Rotate keys regularly
Don't
- Bind to
0.0.0.0unless you need external access - Use
StrictHostKeyChecking noin production - Forward agent (
-A) through untrusted hosts - Use password authentication for automated tunnels
- Run SSH tunnels as root unnecessarily
- Ignore host key changes without verifying the cause
- Use
PermitRootLogin yeson jump hosts - Leave unused forwards open
Example Scenarios
Access Remote Database
ssh -L 3306:127.0.0.1:3306 -N -f user@server.com
Connect to MySQL on localhost:3306 as if it were running locally.
Expose Local Dev Server
ssh -R 80:127.0.0.1:3000 -N -f user@public-server.com
Make your local dev server accessible at public-server.com:80.
SOCKS5 Tunnel through Firewall
ssh -D 1080 -N -f -C user@external-server.com
Route browser/app traffic through an external server to bypass restrictive firewalls.
Multi-Hop to Internal Server
ssh -J jumpuser@bastion.com admin@internal.server
Connect to an internal server through a bastion host in one command.
SSH Key Generation Best Practices
Recommended: Ed25519
ssh-keygen -t ed25519 -C "user@example.com"
- Fastest, smallest keys (256-bit)
- Strong security (EdDSA / Curve25519)
- Supported in OpenSSH 6.5+ (2014)
- Not supported by very old systems or some hardware tokens
Alternative: RSA 4096-bit
ssh-keygen -t rsa -b 4096 -C "user@example.com"
- Universal compatibility (all SSH implementations)
- Larger keys, slower operations
- Minimum 3072 bits recommended (NIST)
- Use when Ed25519 is not supported
| Algorithm | Key Size | Security | Speed | Compatibility |
|---|---|---|---|---|
| Ed25519 | 256 bit | Excellent | Fastest | OpenSSH 6.5+ |
| RSA | 4096 bit | Good | Slower | Universal |
| ECDSA | 256/384/521 bit | Good | Fast | OpenSSH 5.7+ |
| DSA | 1024 bit | Deprecated | Slow | Legacy only |
Useful Commands
# Generate key with custom path
ssh-keygen -t ed25519 -f ~/.ssh/project_key -C "project@example.com"
# Change passphrase on existing key
ssh-keygen -p -f ~/.ssh/id_ed25519
# Show key fingerprint
ssh-keygen -l -f ~/.ssh/id_ed25519.pub
# Copy public key to server
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server
# Convert OpenSSH to PEM format
ssh-keygen -p -m PEM -f ~/.ssh/id_rsa
Tip: Always protect your private key with a passphrase. Set correct permissions: chmod 700 ~/.ssh and chmod 600 ~/.ssh/id_*.
Connection Multiplexing (ControlMaster)
SSH multiplexing allows multiple sessions to share a single TCP connection to the same host. The first connection becomes the "master", and subsequent connections reuse it — eliminating the overhead of TCP handshake, key exchange, and authentication.
Configuration
# ~/.ssh/config
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
ControlMaster auto— automatically use or create a master connectionControlPath— socket file location (%r=user,%h=host,%p=port)ControlPersist 600— keep master alive 600s after last session closes
Management Commands
# Check master status
ssh -O check user@host
# Terminate master connection
ssh -O exit user@host
# Forward new port through existing master
ssh -O forward -L 8080:localhost:80 user@host
# Cancel a specific forward
ssh -O cancel -L 8080:localhost:80 user@host
Create the socket directory first: mkdir -p ~/.ssh/sockets && chmod 700 ~/.ssh/sockets
SSH Tunnels vs Alternatives
| Tool | Type | Best For | Pros | Cons |
|---|---|---|---|---|
| SSH Tunnels | Port forwarding / SOCKS | Quick access to internal services | No extra software, encrypted, everywhere | TCP only, single-host, manual management |
| WireGuard | VPN (Layer 3) | Full network access, always-on VPN | Fast, low overhead, kernel-level, UDP | Requires setup on both ends, root access |
| ngrok / Cloudflare Tunnel | Reverse tunnel SaaS | Exposing local services, demos, webhooks | No server needed, HTTPS/domains, easy setup | Third-party dependency, free tier limits |
| AWS SSM | Cloud-managed tunnel | EC2 access without open SSH ports | No SSH port needed, IAM auth, audit logging | AWS-only, requires SSM agent, higher latency |
| Teleport | Zero-trust access gateway | Enterprise SSH/K8s/DB access with SSO | Certificate-based, audit trail, SSO integration | Complex setup, enterprise pricing |
| Tailscale / ZeroTier | Mesh VPN (overlay) | Connecting devices across networks | Easy peer-to-peer, NAT traversal, no server config | Third-party control plane, not fully self-hosted |
When to use SSH tunnels: Quick, ad-hoc port access to a single service through an existing SSH server. When to consider alternatives: Full network access (WireGuard), public-facing endpoints (ngrok), enterprise compliance (Teleport), or multi-device mesh (Tailscale).