SSH config tricks I keep forgetting
The ~/.ssh/config features I always have to look up, in one place so I don't have to look them up again.
I’ve written a fair amount about SSH on this site — key generation, key permissions, reverse tunnels — but the file I touch more than any other is ~/.ssh/config, and I keep looking the same things up. Writing them down so I don’t have to.
Host blocks
The basic unit. Instead of typing ssh -p 2222 -i ~/.ssh/deploy_key [email protected] every time:
Host prod
HostName prod.example.com
User deploy
Port 2222
IdentityFile ~/.ssh/deploy_key
Now ssh prod just works. So does scp file.tar.gz prod:/tmp/, and rsync, and anything else that speaks SSH.
Wildcards and defaults
Host blocks apply in order, and later directives don’t override earlier ones. So you put specific hosts first and catch-all defaults last:
Host prod staging
User deploy
IdentityFile ~/.ssh/work_key
Host *.internal.example.com
User admin
ProxyJump bastion
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes
UseKeychain yes # macOS only
IdentitiesOnly yes
IdentitiesOnly yes is the one I always forget and the one that causes the most grief. Without it, ssh offers every key in your agent to every server, which can trip rate limits on GitHub or lock you out of servers with MaxAuthTries set low.
ProxyJump (the -J flag)
The modern replacement for the old ProxyCommand ssh bastion nc %h %p incantation. To reach a host that’s only accessible through a bastion:
Host bastion
HostName bastion.example.com
User ops
Host app-*.internal
User deploy
ProxyJump bastion
Now ssh app-01.internal transparently jumps through the bastion. You can chain them: ProxyJump bastion1,bastion2.
Include
The file can include other files, which is what saved me when my config crossed 300 lines:
Include ~/.ssh/config.d/work
Include ~/.ssh/config.d/personal
Include ~/.ssh/config.d/clients/*
Each file can have its own Host blocks. Perfect for separating work from personal, or keeping per-client config in files you can gitignore.
Match (the escape hatch)
When Host isn’t enough — for example, you want different settings based on hostname pattern, username, or even the output of a command:
Match host *.gitlab.com exec "test -f ~/.ssh/gitlab_key"
IdentityFile ~/.ssh/gitlab_key
IdentitiesOnly yes
I use this less than I expected to, but when I need it nothing else works.
The ones I use almost every day
LocalForward—LocalForward 5432 db.internal:5432turnsssh prodinto “open a tunnel to the prod Postgres on localhost:5432.” Connect your local pgcli to it.RemoteForward— the opposite direction, for when you need the remote host to see a local service.ControlMaster auto/ControlPath ~/.ssh/cm_%h_%p_%r/ControlPersist 10m— opens one SSH connection and reuses it for subsequentsshcalls to the same host. Makes repeatedssh prod <cmd>feel instant instead of authenticating from scratch each time.
Host *
ControlMaster auto
ControlPath ~/.ssh/cm_%h_%p_%r
ControlPersist 10m
That block alone probably saves me 20 seconds a day, every day.
The debugging one-liner
When something in the config doesn’t work the way you expect: ssh -vvv host dumps exactly which config files were read, which Host blocks matched, and which options ended up active. 90% of “why isn’t my key being used” questions answer themselves in the -vvv output.