SSH Config Tricks

ProxyJump, multiplexing, key management, and making ~/.ssh/config actually useful.

Config File Basics

The file ~/.ssh/config is read top-to-bottom. The first matching value for each directive wins, so put specific hosts before wildcards.

Host Blocks

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

Wildcards and Defaults

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

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

ProxyJump lets you reach hosts behind a bastion/jump server without manual two-step SSH. Available since OpenSSH 7.3.

Basic Usage

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

Command-line Shorthand

# -J flag (equivalent to ProxyJump)
ssh -J jumpuser@bastion.example.com dbadmin@10.0.1.50

Chaining Multiple Jumps

# 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

ProxyCommand Fallback

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

Multiplexing

Connection multiplexing reuses a single TCP connection for multiple SSH sessions to the same host. This eliminates repeated authentication and TCP handshakes.

Setup

Host *
    ControlMaster auto
    ControlPath ~/.ssh/sockets/%r@%h-%p
    ControlPersist 10m

Create the socket directory first:

mkdir -p ~/.ssh/sockets

How It Works

Managing Multiplexed Connections

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

Key Management

Generating Keys

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

ssh-agent

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

Auto-add Keys via Config

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.

Deploying Public Keys

# 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"

Multiple Keys per Host

# 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

Port Forwarding

Local Forwarding (-L)

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

Remote Forwarding (-R)

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

Dynamic / SOCKS Proxy (-D)

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

Useful Flags

Security

Hardening tips for the SSH server side (/etc/ssh/sshd_config).

Disable Root Login and Passwords

# /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.

Restrict Users

# Only allow specific users or groups
AllowUsers deploy admin
AllowGroups sshusers

# Deny specific users (processed before Allow)
DenyUsers guest test

Change Default Port

# /etc/ssh/sshd_config
Port 2222

# Listen on multiple ports
Port 22
Port 2222

Not true security, but it reduces automated scan noise significantly.

fail2ban

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

Reload After Changes

# Validate config first
sudo sshd -t

# Reload (keeps existing sessions alive)
sudo systemctl reload sshd