Overview
~/.ssh/config gives each host an alias with its own user, key, port, and proxy. For each directive the first value found wins, so put specific Host blocks before Host *. Keep the file private: chmod 600 ~/.ssh/config and chmod 700 ~/.ssh. Generate keys with ssh-keygen -t ed25519 (Ed25519 is the default type); certificate commands are in openssl-commands.
Host blocks
Give every host an alias with an explicit key and IdentitiesOnly yes.
| Directive | Example | Effect |
|---|---|---|
Host | Host myserver | Alias you type; accepts *, ?, and !negation patterns. |
HostName | HostName 203.0.113.7 | Real host or IP. |
User, Port | User deploy, Port 2222 | Remote account and port. |
IdentityFile | IdentityFile ~/.ssh/myserver_ed25519 | Private key to use. |
IdentitiesOnly | IdentitiesOnly yes | Offer only the listed key, not every key in the agent. |
Host myserver
HostName 203.0.113.7
User deploy
IdentityFile ~/.ssh/myserver_ed25519
IdentitiesOnly yes
Jump hosts
ProxyJump replaces the older ProxyCommand ssh -W %h:%p. Chain hosts with commas.
Host internal-db
HostName 10.0.1.50
User postgres
ProxyJump bastion # or: ProxyJump bastion1,bastion2
On the command line: ssh -J bastion internal-db.
Connection reuse
Enable multiplexing so repeated ssh and scp calls skip the handshake.
Host *
ControlMaster auto
ControlPath ~/.ssh/ctrl/%C # %C is a hash, so the socket path stays short
ControlPersist 10m
Create the directory first with mkdir -p ~/.ssh/ctrl. Later ssh and scp runs to the same host skip the handshake. Check or close a master with ssh -O check <host> and ssh -O exit <host>.
Safe defaults
Set these under Host *, then override per host.
| Directive | Value | Reason |
|---|---|---|
AddKeysToAgent | yes | Load a key into ssh-agent on first use. |
ForwardAgent | no (default) | A compromised remote can use a forwarded agent; prefer ProxyJump. |
ServerAliveInterval | 60 | Keepalive every 60 s (default 0 sends none). |
ServerAliveCountMax | 3 (default) | Disconnect after three missed keepalives. |
StrictHostKeyChecking | accept-new | Trust new hosts, reject changed keys; safer than no. |
HashKnownHosts | yes | Hash hostnames in known_hosts (default no). |
Include and Match
Split config by context with Include and Match instead of one huge file.
| Directive | Example | Effect |
|---|---|---|
Include | Include ~/.ssh/config.d/* | Pull in other files; relative paths are under ~/.ssh. |
Match | Match host *.example.com user deploy | Apply directives only when the conditions hold. |
Match exec | Match exec "test -f ~/.work" | Condition on a command’s exit status. |
Place Include before any Host or Match line. After one, it is conditional on that block.
Gotchas
Avoid these; each breaks matching or exposes credentials.
| Gotcha | Fix |
|---|---|
A broad Host * placed first overrides later specific blocks. | Order blocks specific first, defaults last. |
ForwardAgent yes under Host * exposes your agent on every server. | Enable it per trusted host only. |
A stale ControlMaster socket makes ssh fail with a socket error. | ssh -O exit <host> or delete the socket. |
Without IdentitiesOnly, many agent keys can exhaust the server’s MaxAuthTries. | Add IdentitiesOnly yes with an explicit IdentityFile. |
| A config writable by others, or a private key readable by others, is rejected. | Fix permissions as in the overview. |