ProxyJump, multiplexing, key management, and making ~/.ssh/config actually useful.
The file ~/.ssh/config is read top-to-bottom. The first matching value for each directive wins, so put specific hosts before wildcards.
Each Host block defines settings for one or more hostnames or aliases.
# Alias: type "ssh prod" instead of the full command
Host prod
HostName 203.0.113.50
User deploy
Port 2222
IdentityFile ~/.ssh/id_prod_ed25519
# Multiple names for one block
Host staging stg
HostName staging.example.com
User deploy
Use * to set defaults for all connections. Use ? to match a single character.
# Defaults for everything
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes
IdentitiesOnly yes
# All hosts under example.com
Host *.example.com
User admin
IdentityFile ~/.ssh/id_work
Match blocks allow conditional configuration based on criteria like hostname, user, or the result of a command.
# Only apply when connecting as root
Match User root
IdentityFile ~/.ssh/id_root
# Conditional on network (exec returns 0 = true)
Match Host *.internal exec "ip route | grep -q 10.0.0.0"
ProxyJump none
ProxyJump lets you reach hosts behind a bastion/jump server without manual two-step SSH. Available since OpenSSH 7.3.
# In ~/.ssh/config
Host bastion
HostName bastion.example.com
User jumpuser
IdentityFile ~/.ssh/id_bastion
Host internal-db
HostName 10.0.1.50
User dbadmin
ProxyJump bastion
Now ssh internal-db transparently hops through the bastion.
# -J flag (equivalent to ProxyJump)
ssh -J jumpuser@bastion.example.com dbadmin@10.0.1.50
# Hop through two bastions
Host deep-server
HostName 10.10.1.5
User admin
ProxyJump bastion1,bastion2
# Command-line equivalent
ssh -J user@bastion1,user@bastion2 admin@10.10.1.5
For older OpenSSH versions that lack ProxyJump, use ProxyCommand.
Host internal-db
HostName 10.0.1.50
User dbadmin
ProxyCommand ssh -W %h:%p bastion
Connection multiplexing reuses a single TCP connection for multiple SSH sessions to the same host. This eliminates repeated authentication and TCP handshakes.
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 10m
Create the socket directory first:
mkdir -p ~/.ssh/sockets
%r = remote user, %h = host, %p = port.# Check status of a master connection
ssh -O check prod
# Gracefully close a master connection
ssh -O exit prod
# Force-stop (if exit hangs)
ssh -O stop prod
Tip: If a multiplexed connection goes stale (e.g., network drop), delete the socket file manually: rm ~/.ssh/sockets/deploy@203.0.113.50-2222.
Ed25519 is the recommended algorithm — small keys, fast, and secure.
# Generate an Ed25519 key with a comment
ssh-keygen -t ed25519 -C "you@example.com" -f ~/.ssh/id_work
# If you need RSA (legacy systems), use at least 4096 bits
ssh-keygen -t rsa -b 4096 -C "you@example.com" -f ~/.ssh/id_legacy
The agent holds decrypted private keys in memory so you only type the passphrase once per session.
# Start the agent (usually done by your shell profile)
eval "$(ssh-agent -s)"
# Add a key (prompts for passphrase)
ssh-add ~/.ssh/id_work
# List loaded keys
ssh-add -l
# Remove all keys from agent
ssh-add -D
Host *
AddKeysToAgent yes
IdentitiesOnly yes
AddKeysToAgent yes loads the key into the agent on first use. IdentitiesOnly yes prevents SSH from trying every key in the agent — it only offers the key specified by IdentityFile.
# Copy your public key to a remote server
ssh-copy-id -i ~/.ssh/id_work.pub user@server.example.com
# Manual method (if ssh-copy-id is unavailable)
cat ~/.ssh/id_work.pub | ssh user@server "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
# Use different keys for different GitHub accounts
Host github-personal
HostName github.com
User git
IdentityFile ~/.ssh/id_personal
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_work
Clone with: git clone github-work:company/repo.git
Forward a port on your machine to a port on the remote side. Common for accessing services behind a firewall.
# Access remote PostgreSQL (port 5432) on localhost:5433
ssh -L 5433:localhost:5432 dbserver
# Access a service on a different host via the SSH server
ssh -L 8080:internal-app.lan:80 bastion
# Config file equivalent
Host db-tunnel
HostName dbserver.example.com
User admin
LocalForward 5433 localhost:5432
# Don't open a shell, just forward
RequestTTY no
Expose a local service to the remote network. Useful for sharing a dev server or webhook testing.
# Make your local port 3000 accessible as port 8080 on the remote host
ssh -R 8080:localhost:3000 remote-server
# Config file equivalent
Host expose-dev
HostName remote-server.example.com
User dev
RemoteForward 8080 localhost:3000
Note: The remote server needs GatewayPorts yes in its sshd_config to allow connections from other machines (not just localhost).
Creates a local SOCKS5 proxy. All traffic routed through it exits from the SSH server.
# Start a SOCKS proxy on localhost:1080
ssh -D 1080 -N remote-server
# Then configure your browser or use curl:
curl --proxy socks5h://localhost:1080 https://ifconfig.me
# Config file equivalent
Host socks-proxy
HostName remote-server.example.com
User tunnel
DynamicForward 1080
RequestTTY no
-N — no remote command (just forward ports).-f — go to background after authentication.ssh -fNL 5433:localhost:5432 dbserver — background tunnel.Hardening tips for the SSH server side (/etc/ssh/sshd_config).
# /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
Always test with a second terminal before closing your current session after changing sshd_config.
# Only allow specific users or groups
AllowUsers deploy admin
AllowGroups sshusers
# Deny specific users (processed before Allow)
DenyUsers guest test
# /etc/ssh/sshd_config
Port 2222
# Listen on multiple ports
Port 22
Port 2222
Not true security, but it reduces automated scan noise significantly.
Automatically bans IPs after repeated failed login attempts.
# Install
sudo apt install fail2ban # Debian/Ubuntu
sudo dnf install fail2ban # Fedora/RHEL
# Create local override (never edit jail.conf directly)
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
# /etc/fail2ban/jail.local — SSH section
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600
findtime = 600
# Start and enable
sudo systemctl enable --now fail2ban
# Check banned IPs
sudo fail2ban-client status sshd
# Unban an IP
sudo fail2ban-client set sshd unbanip 203.0.113.99
# Validate config first
sudo sshd -t
# Reload (keeps existing sessions alive)
sudo systemctl reload sshd