← Back to Blog SSH

SSH Complete Guide: Keys, Config Files, Tunnels & Jump Hosts (2026)

🔧 Interactive SSH Tools — 100% Private, No Server

Build, inspect, and learn SSH without uploading anything:

1. What is SSH and How Does It Work?

SSH — Secure Shell — is a cryptographic network protocol defined in RFC 4251 through 4254. It provides a secure, encrypted channel between two machines over an untrusted network. Every major cloud provider (AWS, GCP, Azure, DigitalOcean), every Linux VPS, and most network hardware supports SSH as the primary remote-access mechanism.

Before SSH became standard in the late 1990s, administrators used Telnet and rlogin — protocols that transmitted usernames, passwords, and all session data in plain text. A passive network observer could trivially steal credentials. SSH solved this by layering all communication inside strong encryption.

The SSH Handshake — Step by Step

When you type ssh user@server, the following sequence happens in milliseconds:

  1. TCP connection — your client opens a TCP connection to port 22 (or a custom port) on the server.
  2. Protocol negotiation — both sides exchange SSH protocol version strings.
  3. Algorithm negotiation (KEXINIT) — client and server agree on key exchange algorithms, host key types, ciphers, MACs, and compression.
  4. Key exchange (KEX) — using Diffie-Hellman or ECDH, the two parties derive a shared secret without ever transmitting it. This becomes the session key.
  5. Server authentication — the server proves its identity by signing a value with its host private key. Your client verifies this against ~/.ssh/known_hosts.
  6. User authentication — the server challenges your client. You authenticate via public key, password, or certificate.
  7. Encrypted session — all further traffic is symmetrically encrypted using the negotiated session key.

This design means even if someone captures every byte of your SSH session on the wire, they cannot decrypt it without the session key — which was never transmitted.

SSH Protocol Versions

SSH-1 (1995) had several cryptographic weaknesses and is considered completely insecure. All modern systems use SSH-2 (RFC 4251, 2006). If a server advertises SSH-1, treat it as a security incident. OpenSSH dropped SSH-1 support in version 7.6 (2017).

2. SSH Key Types: Ed25519 vs RSA vs ECDSA

SSH public-key authentication requires a matched key pair: a private key you keep secret, and a public key you deploy to servers. There are four common algorithms:

AlgorithmKey SizeSpeedSecurityCompatibilityRecommendation
Ed25519256-bit (fixed)Very fastExcellentOpenSSH 6.5+ (2014)✅ Use this
ECDSA (P-256)256–521-bitFastGood*OpenSSH 5.7+ (2011)⚠️ Acceptable
RSA2048–4096-bitSlowerGood at 4096Universal⚠️ Legacy only
DSA1024-bit (fixed)SlowBrokenRemoved in OpenSSH 7.0❌ Never use

* ECDSA security depends on strong randomness during signing. Ed25519 is deterministic and immune to weak-RNG attacks.

Use Ed25519 by default. It produces a 68-character public key (vs 728 characters for RSA-4096), signs and verifies faster, and has stronger security guarantees. The only reason to use RSA-4096 in 2026 is compatibility with systems running OpenSSH older than 6.5, which is extremely rare.

You can use our browser-only SSH Key Generator to generate Ed25519 or RSA key pairs entirely locally — no server, no upload, no logging.

3. Generating SSH Keys

The ssh-keygen utility comes pre-installed with OpenSSH on Linux, macOS, and Windows 10+. Here are the most useful key generation commands:

# Generate an Ed25519 key (recommended, 2026)
ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/id_ed25519

# Generate RSA-4096 for legacy servers that don't support Ed25519
ssh-keygen -t rsa -b 4096 -C "[email protected]" -f ~/.ssh/id_rsa_legacy

# View the public key (safe to share)
cat ~/.ssh/id_ed25519.pub

# Copy the public key to a remote server
ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]

Key Generation Options Explained

  • -t: Algorithm type (ed25519, rsa, ecdsa).
  • -b: Bit length. Only meaningful for RSA (use 4096). Ed25519 ignores this.
  • -C: Comment embedded in the public key. Use your email or a descriptive label like deploy-key-prod-2026.
  • -f: Output filename. Always specify this when generating multiple keys to avoid overwriting your default key.
  • -N: Passphrase (use -N "" for no passphrase — only for automated deploy keys with restricted permissions).

Passphrases and the SSH Agent

Every private key should have a passphrase. This encrypts the key at rest — if someone steals your ~/.ssh/ directory, they still cannot use the keys without the passphrase. The passphrase only unlocks the key; it is never sent to the server.

To avoid typing the passphrase repeatedly, add the key to ssh-agent: ssh-add ~/.ssh/id_ed25519. The agent holds the decrypted key in memory for the session. On macOS, adding UseKeychain yes to your SSH config persists the passphrase across reboots via Keychain.

File Permissions — The Silent Breaker

OpenSSH will silently refuse to use a private key with open permissions. Set these immediately after generating:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519      # private key
chmod 644 ~/.ssh/id_ed25519.pub  # public key (can be world-readable)
chmod 600 ~/.ssh/config
chmod 600 ~/.ssh/authorized_keys

4. Managing authorized_keys

The ~/.ssh/authorized_keys file on a server lists all public keys permitted to authenticate. One public key per line. You can have hundreds of entries in one file.

# On the remote server, authorized_keys format:
# ~/.ssh/authorized_keys

# Standard entry
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [email protected]

# Restrict to specific commands only
command="backup-script.sh",no-pty,no-port-forwarding ssh-ed25519 AAAA... backup-key

# Restrict by source IP
from="203.0.113.0/24" ssh-ed25519 AAAA... office-deploy-key

Key Options in authorized_keys

Each line in authorized_keys can have prefix options that restrict how and when that key is valid:

  • command="script.sh" — the connection can only run that specific command, not a shell.
  • no-pty — prevents pseudoterminal allocation (useful for automated scripts).
  • no-port-forwarding — disables tunneling for that key.
  • from="IP" — restricts which source IPs can authenticate with that key.
  • expiry-time="20261231" — key expires on that date (OpenSSH 8.2+).

Deploying Keys Safely

The safest way to add a key to a server is ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server. This appends the key to authorized_keys with correct permissions and avoids the common mistake of overwriting the file. If you do not have ssh-copy-id, the manual equivalent is:

cat ~/.ssh/id_ed25519.pub | ssh user@server   "mkdir -p ~/.ssh && chmod 700 ~/.ssh &&    cat >> ~/.ssh/authorized_keys &&    chmod 600 ~/.ssh/authorized_keys"

5. The ~/.ssh/config File: Complete Reference

The SSH client config file is the single biggest productivity improvement for anyone managing more than two servers. Instead of memorizing ssh [email protected] -p 2222 -i ~/.ssh/id_ed25519_prod, you just type ssh prod.

Use our SSH Config Generator to build your config visually with zero syntax errors, then paste the output into ~/.ssh/config.

# ~/.ssh/config
# Permissions must be 600: chmod 600 ~/.ssh/config

# ── Global defaults for all hosts ───────────────────────────
Host *
  AddKeysToAgent yes
  UseKeychain yes          # macOS only
  ServerAliveInterval 60
  ServerAliveCountMax 3
  IdentitiesOnly yes

# ── Production web server ────────────────────────────────────
Host prod
  HostName 198.51.100.24
  User deploy
  Port 2222
  IdentityFile ~/.ssh/id_ed25519_prod

# ── Staging server ───────────────────────────────────────────
Host staging
  HostName 198.51.100.50
  User deploy
  IdentityFile ~/.ssh/id_ed25519_staging

# ── Bastion / jump host ──────────────────────────────────────
Host bastion
  HostName 203.0.113.10
  User bastion-user
  IdentityFile ~/.ssh/id_ed25519_bastion

# ── Internal DB (reached via bastion) ───────────────────────
Host internal-db
  HostName 10.0.1.50
  User db-admin
  IdentityFile ~/.ssh/id_ed25519_db
  ProxyJump bastion

# ── GitHub (shorthand) ───────────────────────────────────────
Host github.com
  IdentityFile ~/.ssh/id_ed25519_github
  User git

Config File Precedence Rules

SSH evaluates the config file top-to-bottom. The first matching value for a parameter wins. This means global defaults in Host * should go at the bottom, because specific host blocks at the top take precedence. If you put Host * first with a specific IdentityFile, that overrides every specific host block below it.

Most Useful Config Directives

DirectiveEffectExample
HostNameReal hostname or IP203.0.113.10
UserSSH usernamedeploy
PortSSH port2222
IdentityFilePath to private key~/.ssh/id_ed25519_prod
IdentitiesOnlyOnly use specified keysyes
ProxyJumpJump through a bastionbastion
ForwardAgentForward SSH agentyes (use carefully)
LocalForwardLocal port forward5432 db.internal:5432
ServerAliveIntervalKeepalive seconds60
StrictHostKeyCheckingHost key checkingaccept-new (safer than no)
ControlMasterMultiplexingauto
ControlPathMultiplex socket path~/.ssh/cm-%r@%h:%p

Wildcards and Patterns in Host

The Host directive supports wildcards: Host *.staging.example.com matches any subdomain. Host 10.0.1.* matches any IP in that range. You can also negate patterns: Host * !bastion matches everything except the bastion alias.

6. Jump Hosts and ProxyJump

A jump host (bastion host) is an internet-facing server that acts as a secure gateway to servers on a private network. Private servers (like databases, Kubernetes nodes, or internal APIs) never have public IPs — you must go through the bastion.

The Modern Way: ProxyJump

ProxyJump (added in OpenSSH 7.3, 2016) is the clean, modern approach. It creates a nested SSH connection: your client connects to the jump host, then the jump host's SSH client (running inside the same SSH session) connects to the target. The target server sees a connection from the jump host's IP.

# One-liner with -J flag
ssh -J [email protected] [email protected]

# With SSH config (much cleaner)
# Add to ~/.ssh/config:
# Host internal-db
#   HostName 10.0.1.50
#   User db-admin
#   ProxyJump bastion
#
# Then just:
ssh internal-db

Chaining Multiple Jump Hosts

If your architecture has multiple network hops (e.g., internet → DMZ bastion → production network bastion → target), you can chain them:

# Two jump hops
ssh -J user@dmz-bastion,user@prod-bastion user@final-server

# In SSH config:
# Host final-server
#   HostName 10.10.2.100
#   ProxyJump dmz-bastion,prod-bastion

ProxyJump vs ProxyCommand

The older approach used ProxyCommand with ssh -W. ProxyJump is strictly better: it is simpler, supports agent forwarding correctly, and OpenSSH optimizes the connection path internally. Only use ProxyCommand if you need a non-SSH proxy (e.g., nc or socat for custom routing).

7. SSH Port Forwarding and Tunnels

SSH port forwarding (SSH tunneling) lets you route arbitrary TCP traffic through an SSH connection. It is how developers securely access databases, admin UIs, and internal services without exposing them to the internet.

# Local port forwarding: reach remote DB at localhost:5432
ssh -L 5432:postgres.internal:5432 -N -f bastion

# Remote port forwarding: expose local dev server on remote port
ssh -R 8080:localhost:3000 -N -f your-server

# Dynamic SOCKS5 proxy (all traffic through the tunnel)
ssh -D 1080 -N -f your-server

# Persistent tunnel using SSH config (add to ~/.ssh/config):
# Host db-tunnel
#   HostName bastion.example.com
#   User bastion-user
#   LocalForward 5432 postgres.internal:5432
#   ServerAliveInterval 30

Local Port Forwarding (-L)

Local forwarding binds a port on your local machine and forwards connections through the SSH server to a target address. The target is resolved by the SSH server, not your machine. This is how you reach a database on a private network:

# Your local :5432 → SSH server → postgres.internal:5432
ssh -L 5432:postgres.internal:5432 -N -f user@jump-server

# Now connect your DB client to localhost:5432
psql -h localhost -p 5432 -U app_user mydb

-N means "don't run a command after connecting" (just forward). -f sends the process to the background.

Remote Port Forwarding (-R)

Remote forwarding binds a port on the remote server and forwards connections back to your local machine. This lets you temporarily expose a local dev server to the internet through a public server you control:

# Remote server's :8080 → back to your localhost:3000
ssh -R 8080:localhost:3000 -N user@your-public-server
# Now visiting http://your-public-server:8080 hits your local app

Dynamic SOCKS5 Proxy (-D)

Dynamic forwarding creates a SOCKS5 proxy on your local machine that routes all traffic through the SSH server. Configure your browser or system proxy to localhost:1080 and all your web traffic exits from the server's network — useful for accessing geo-restricted resources or testing from different regions:

ssh -D 1080 -N -f user@your-server
# Configure browser: SOCKS5 proxy → localhost:1080

8. SSH Agent and Agent Forwarding

ssh-agent is a background process that holds your decrypted private keys in memory. When you run ssh-add ~/.ssh/id_ed25519, the agent stores the key. Every SSH connection then asks the agent to perform authentication operations — you enter your passphrase once per session.

# Start the agent (usually auto-started by your desktop session)
eval "$(ssh-agent -s)"

# Add a key with passphrase caching
ssh-add ~/.ssh/id_ed25519

# Add with a time limit (key auto-expires after 8 hours)
ssh-add -t 28800 ~/.ssh/id_ed25519

# List loaded keys
ssh-add -l

# Remove all keys from agent
ssh-add -D

Agent Forwarding: Power and Risk

ForwardAgent yes in your SSH config causes your local agent to be made available on the remote server. While connected to that server, it can make further SSH connections (e.g., git pull from a private repo) using your local key — your private key never leaves your machine.

The risk: anyone with root access on the remote server can use your forwarded agent socket to authenticate as you to other systems during your session. The socket disappears when you disconnect, but the window is real. Best practice: only forward the agent to servers you fully trust, and consider using ssh-add -c (confirms each use) for sensitive keys.

9. SSH Multiplexing (ControlMaster)

Every SSH connection requires a full TLS-like handshake. If you run many short-lived SSH commands (e.g., git pull, Ansible tasks, rsync), this overhead adds up. SSH multiplexing reuses a single connection for multiple sessions.

# Add to ~/.ssh/config:
Host your-server
  HostName 203.0.113.10
  ControlMaster auto
  ControlPath ~/.ssh/cm-%r@%h:%p
  ControlPersist 10m

# First connection opens the master; subsequent connections reuse it instantly
# ControlPersist 10m keeps the master alive 10 minutes after the last session closes

With multiplexing, ssh your-server date goes from ~300ms (handshake) to ~15ms (socket lookup). For Ansible running 50 tasks, this saves minutes per playbook run.

10. Server-Side Hardening (sshd_config)

The server-side SSH daemon is configured at /etc/ssh/sshd_config. Poor defaults on a fresh cloud instance are responsible for a significant share of server compromises. Here is a production-ready hardening configuration:

# /etc/ssh/sshd_config — server hardening checklist
Port 2222                    # non-default port
PermitRootLogin no           # never allow direct root login
PasswordAuthentication no    # key-only auth
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
AllowUsers deploy admin      # explicit allowlist
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 5
X11Forwarding no
AllowAgentForwarding no      # disable unless needed
ClientAliveInterval 300
ClientAliveCountMax 2

# After editing, verify config and reload:
# sshd -t && systemctl reload sshd

Host Keys and known_hosts

When you first connect to a server, SSH asks you to verify its host key fingerprint. If you accept, the key is stored in ~/.ssh/known_hosts. On subsequent connections, SSH compares the server's key to this stored value. A mismatch triggers a loud warning — this protects you against man-in-the-middle attacks.

To get the host key fingerprint before connecting (e.g., from your cloud provider's console), use:

# On the server (run this before exposing SSH):
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub

# Remove a stale known_hosts entry (after server rebuild):
ssh-keygen -R hostname-or-ip

Certificate-Based Authentication (Advanced)

For organizations with many servers and users, managing authorized_keys files at scale is painful. SSH certificates (via a Certificate Authority) solve this: users get short-lived signed certificates instead of deploying public keys to every server. This is how large companies (GitHub, Facebook) handle SSH at scale. OpenSSH supports this natively via TrustedUserCAKeys in sshd_config.

11. Troubleshooting SSH Connections

SSH's verbose mode is your best diagnostic tool. Add -v, -vv, or -vvv for increasing verbosity:

ssh -vvv user@server 2>&1 | head -80

Common Issues and Fixes

Error / SymptomLikely CauseFix
Permission denied (publickey)Wrong key, wrong user, bad permissionsCheck -vvv output; verify authorized_keys perms (600); confirm correct -i key
Connection timed outFirewall blocking port 22Check security groups / iptables; try nc -zvw5 host 22
Host key verification failedServer rebuilt or MITMRun ssh-keygen -R host if server rebuilt; investigate if unexpected
WARNING: UNPROTECTED PRIVATE KEYKey has wrong permissionschmod 600 ~/.ssh/id_ed25519
Too many authentication failuresAgent offering too many keysAdd IdentitiesOnly yes to SSH config
Broken pipe / connection dropsNo keepalive configuredAdd ServerAliveInterval 60 to SSH config
ProxyJump authentication failsNo agent forwarding to jump hostAdd ForwardAgent yes for the jump host block

Reading SSH Logs on the Server

# Most Linux distributions:
journalctl -u sshd -f

# Or directly:
tail -f /var/log/auth.log     # Debian/Ubuntu
tail -f /var/log/secure       # RHEL/CentOS/Fedora

12. Essential SSH Command Reference

Use our SSH Command Builder to assemble complex SSH commands visually, with options for tunneling, agent forwarding, custom ports, and more.

CommandPurpose
ssh user@hostBasic connection
ssh -p 2222 user@hostCustom port
ssh -i ~/.ssh/key user@hostSpecific identity file
ssh -J jump user@targetJump through bastion
ssh -L 8080:internal:80 user@hostLocal port forward
ssh -R 8080:localhost:3000 user@hostRemote port forward
ssh -D 1080 -N user@hostSOCKS5 proxy
ssh -A user@hostAgent forwarding
ssh -N -f user@hostBackground, no shell
ssh -o StrictHostKeyChecking=accept-new user@hostAuto-accept new host keys
ssh-copy-id -i key.pub user@hostDeploy public key
ssh-keygen -R hostnameRemove from known_hosts
scp file.txt user@host:/path/Copy file to server
rsync -avz -e ssh ./dir user@host:/path/Sync directory over SSH

Frequently Asked Questions

What is SSH and how does it work?
SSH (Secure Shell) is a cryptographic network protocol that creates an encrypted tunnel between two machines. It uses asymmetric key exchange (Diffie-Hellman / ECDH) to derive a shared session key, then encrypts all traffic symmetrically. Authentication can use passwords, public-key pairs, or certificates. All modern remote server access relies on SSH.
Which SSH key type should I use in 2026?
Ed25519 is the recommended key type in 2026. It produces shorter keys, signs faster than RSA, and is based on elliptic curve cryptography with no known practical attacks. Use ssh-keygen -t ed25519. Only fall back to RSA-4096 for legacy systems running OpenSSH older than 6.5.
Where is the SSH config file located?
The per-user SSH config file is at ~/.ssh/config on Linux and macOS, and at C:\Users\YourName\.ssh\config on Windows. Permissions must be 600 (chmod 600 ~/.ssh/config). The system-wide config is at /etc/ssh/ssh_config.
What is an SSH jump host (ProxyJump)?
A jump host is an intermediate server you pass through to reach servers on a private network. Configure it with ProxyJump bastion in ~/.ssh/config under the target host block. On the command line, use ssh -J user@bastion user@target. ProxyJump replaced the older ProxyCommand pattern in OpenSSH 7.3+.
How do I set up SSH key-based authentication?
Run ssh-keygen -t ed25519 to generate a key pair. Deploy the public key with ssh-copy-id user@server. Verify you can log in with the key, then harden the server by setting PasswordAuthentication no in /etc/ssh/sshd_config and running systemctl reload sshd.
What is SSH agent forwarding and when should I use it?
Agent forwarding (ForwardAgent yes) makes your local SSH keys available on a remote server for further SSH connections (e.g., git operations). Use it only on servers you fully trust — a root user on the remote machine can use your forwarded agent socket during your session. For most use cases, ProxyJump is safer than agent forwarding.
How do I set up SSH port forwarding?
Local forwarding: ssh -L localport:remote-host:remoteport -N user@jump — your local port connects through the jump host to the remote service. Remote forwarding: ssh -R remoteport:localhost:localport user@server — exposes your local service on the server's port. Dynamic (-D 1080) creates a SOCKS5 proxy.
How do I harden SSH on my server?
Key steps: disable root login (PermitRootLogin no), disable password auth (PasswordAuthentication no), change the default port, restrict allowed users (AllowUsers deploy), enable fail2ban, set MaxAuthTries 3 and LoginGraceTime 30. Always run sshd -t to validate config before reloading.