Most people use ~/.ssh/config as a glorified shortcut file: Host server with a HostName and a User, save five seconds of typing. That’s the entry point, and it’s fine. But the file is a much more powerful configuration system than the average sysadmin uses, and a one-time investment in setting it up properly pays back every single day after.
Here are the features that turn it from “shortcut file” into “the thing that makes your shell environment 5–10× more pleasant to work in.”
Connection multiplexing with ControlMaster
Host *
ControlMaster auto
ControlPath ~/.ssh/cm/%C
ControlPersist 600What this does: the first ssh to a host performs the full TCP handshake + key exchange + auth. SSH then keeps that authenticated connection alive in the background for 10 minutes (ControlPersist 600). Subsequent ssh, scp, rsync, or git push commands to the same host don’t re-authenticate — they share the existing connection. On a 100 ms link, the difference is “every command takes 1500 ms” vs “the first one takes 1500 ms, every subsequent one is instant.”
Make the directory once: mkdir -p ~/.ssh/cm && chmod 700 ~/.ssh/cm. The %C placeholder hashes the host/user/port together so the socket name is unique per target. Don’t use %h or %p directly — those produce collisions on long hostnames.
Per-host settings
Host work-bastion
HostName bastion.work.example.com
User john.doe
Port 2222
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
Host work-internal
HostName 10.20.30.40
User john.doe
ProxyJump work-bastion
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
Host personal
HostName home.duckdns.org
User me
Port 22
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yesThe non-obvious one is IdentitiesOnly yes. Without it, your SSH client offers every key in your agent to every host you connect to. If you have 5 keys loaded and the server has a tight MaxAuthTries, you can be denied auth after offering 5 wrong keys before SSH even gets to your correct one. With IdentitiesOnly yes, the client only offers the key listed in IdentityFile — fewer wasted auth attempts, fewer “too many authentication failures” errors.
ProxyJump for bastion-fronted servers
Host private
HostName private.internal.example.com
User deploy
ProxyJump bastionYour private key never lands on the bastion. The bastion is just a TCP tunnel; auth happens on your laptop both times. Combined with ControlMaster above, the second hop also gets multiplexed, so reaching the inner host is instant after the first connect.
Wildcard rules and overrides
# Defaults that apply to every host unless overridden later
Host *
ServerAliveInterval 30
ServerAliveCountMax 3
ControlMaster auto
ControlPath ~/.ssh/cm/%C
ControlPersist 600
AddKeysToAgent yes
UseKeychain yes # macOS only — load passphrase from Keychain
HashKnownHosts no # readable known_hosts so I can grep it
# GitHub-specific
Host github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
# Internal corporate hosts: more aggressive keepalive, ProxyJump
Host *.work.internal
User john.doe
ProxyJump work-bastion
IdentityFile ~/.ssh/id_ed25519_work
ServerAliveInterval 15Order matters: SSH walks ~/.ssh/config top to bottom and applies the first matching directive for each setting. Put specific hosts at the top, broad wildcards at the bottom. The order in the example above (specific → corporate-pattern → global default) is the canonical structure.
Match — conditional config
Match host *.dev,*.staging exec "ping -c1 -W1 vpn.example.com >/dev/null"
ProxyJump vpn-jumphost
IdentityFile ~/.ssh/id_ed25519_devMatch blocks let you change config based on conditions. The example above uses exec to test if the corporate VPN’s pingable — if it is, route via the jump host; if not, fall through to other rules. Niche but useful when your network changes (laptop on home Wi-Fi vs at the office).
Includes — modular config
Include ~/.ssh/config.d/*.conf
Include ~/.ssh/config.work
Include ~/.ssh/config.personalSplit a 200-line ~/.ssh/config into work, personal, project-specific files. Useful if you want to commit your personal SSH config to dotfiles in a public repo without leaking work hostnames; put the work bits in a separate file that stays gitignored.
A few less-common ones worth knowing about
RemoteCommand— run a specific command upon connecting. Useful for “always start in tmux”:RemoteCommand tmux new -A -s mainwithRequestTTY yes.SetEnv— pass an env var to the remote shell. Servers usually filter most of these viaAcceptEnvon their end, but useful for things likeSetEnv TERM=xterm-256colorwhen the remote shell shows up monochrome.StrictHostKeyChecking accept-new— automatically accept the host key the first time you connect to a new host (and warn if it ever changes). Safer thanStrictHostKeyChecking no; avoids the every-time interactive prompt.VisualHostKey yes— show ASCII-art fingerprint of the host key on connect. Lets you eyeball “yep that’s the same server” without comparing fingerprints character-by-character.RequestTTY force— for servers that drop you into a non-interactive shell otherwise (rare but real on some shared bastions).
A starter ~/.ssh/config worth copying
# ~/.ssh/config — sane defaults + per-host examples
Host *
ServerAliveInterval 30
ServerAliveCountMax 3
ControlMaster auto
ControlPath ~/.ssh/cm/%C
ControlPersist 600
AddKeysToAgent yes
UseKeychain yes
StrictHostKeyChecking accept-new
HashKnownHosts no
Host github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
IdentitiesOnly yes
Host bastion
HostName bastion.example.com
User you
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
Host private
HostName 10.0.5.20
User you
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yesDrop that in, run mkdir -p ~/.ssh/cm && chmod 700 ~/.ssh/cm, customize the host blocks. The next time you ssh private, you’re going through a bastion you never have to think about, with multiplexing for instant subsequent connections, with the right key for that host, and with keepalives that survive a Wi-Fi blink. That’s a solid daily-driver setup that took five minutes to write.
