If you’ve ever typed ssh -i ~/.ssh/work_ed25519 -p 2222 -o ServerAliveInterval=30 [email protected] more than twice, you’ve already earned back the ten minutes it takes to learn ssh_config. It’s the single highest-leverage file in your SSH setup: it turns that whole line into ssh web1, and it’s read automatically by ssh, scp, sftp, rsync, git, and anything else that shells out to SSH.
Here’s the practical tour — the directives that actually earn their place, and the traps worth knowing.
Where it lives and how it’s read
Your personal config is ~/.ssh/config (create it if it doesn’t exist; chmod 600). There’s also a system-wide /etc/ssh/ssh_config. The rules that trip people up:
- First match wins, not most specific. SSH reads top to bottom and the first value it sees for each option is the one it uses. Put your specific hosts at the top and broad
Host *defaults at the bottom. - Every setting lives under a
Hostblock (a pattern), indented by convention (indentation isn’t required, but read the file your future self will thank you for).
The building block: a host alias
Host web1
HostName 203.0.113.10
User deploy
Port 2222
IdentityFile ~/.ssh/work_ed25519
Now ssh web1 expands to the full command. So does scp file.tar web1:/srv/, sftp web1, and rsync -a ./dist/ web1:/var/www/. The alias is the whole point: name the server once, use the name everywhere.
Directives that earn their place
ProxyJump — reach servers through a bastion. The modern replacement for the old ProxyCommand ... netcat incantation:
Host db1
HostName 10.0.0.5 # private address, only reachable from the bastion
User deploy
ProxyJump bastion
Host bastion
HostName bastion.example.com
User jump
ssh db1 now transparently hops through bastion to reach a host with no public address. Chain them with commas (ProxyJump a,b,c) for deeper networks.
Keepalives — stop idle sessions from dying. The fix for client_loop: send disconnect: Broken pipe when you look away:
Host *
ServerAliveInterval 30
ServerAliveCountMax 4
A packet every 30 seconds keeps NAT routers and firewalls from forgetting your connection; four missed replies (two minutes) before it gives up.
IdentitiesOnly — offer only the right key. If you carry several keys, SSH offers them all and a strict server replies Too many authentication failures. Pin one key per host and stop the guessing:
Host github.com
IdentityFile ~/.ssh/personal_ed25519
IdentitiesOnly yes
AddKeysToAgent — unlock a passphrase-protected key once:
Host *
AddKeysToAgent yes
Patterns and wildcards — configure a fleet in one block:
Host *.staging.example.com
User deploy
IdentityFile ~/.ssh/staging_ed25519
Host 10.0.*
ProxyJump bastion
A realistic config, top to bottom
# Specific hosts first (first match wins)
Host web1
HostName 203.0.113.10
User deploy
Port 2222
IdentityFile ~/.ssh/work_ed25519
Host db1
HostName 10.0.0.5
User deploy
ProxyJump bastion
Host bastion
HostName bastion.example.com
User jump
IdentityFile ~/.ssh/work_ed25519
Host github.com
IdentityFile ~/.ssh/personal_ed25519
IdentitiesOnly yes
# Broad defaults last
Host *
ServerAliveInterval 30
ServerAliveCountMax 4
AddKeysToAgent yes
Split it up as it grows: Include ~/.ssh/config.d/* at the top of the file lets you keep one file per project or client.
Two traps
- “First match wins” bites you when a
Host *block near the top sets a value you meant to override below — it won’t override, because the earlier value already won. Defaults go at the bottom. - Comments are
#at line start. Trailing comments after a value can be parsed as part of the value on some options — keep comments on their own lines.
When the config file isn’t enough
ssh_config is excellent and everyone should have one. Its edges show when the setup gets bigger than a text file: you’re syncing it across machines by hand, sharing sanitized versions with teammates, remembering which passphrase goes with which key, or trying to see at a glance which of forty hosts is even online.
That’s the seam Termalin fills. It keeps the same per-host settings — hostname, user, port, key, jump host, keepalives — but attached to the host as structured data you organize into folders and tags, unlocks your keys once through a built-in agent, verifies each server on first connect, and shows live status for every host in the list. It imports your existing PuTTY sessions, and its host list syncs across your machines end-to-end encrypted. The file scales to a point; a client that keeps the state for you scales past it.
Either way, the principle holds: name a server once, and never retype its flags again.
Termalin is a free, cross-platform SSH client that manages per-host settings, keys and jump hosts for you — download it, or read how it handles keys safely.