Skip to content

The SSH Config File: Type Less, Connect Faster

SSH has been quietly carrying developers into remote machines since 1995 — older than most of the frameworks you’ll deploy with it, and certain to outlive all of them. The tool itself is a masterpiece. The way most people use it is not:

Terminal window
ssh -i ~/.ssh/id_rsa_deploy -p 2222 [email protected]

A command with the readability of a number plate, re-Googled weekly and mistyped monthly. The fix has been sitting in ~/.ssh/config all along: name your hosts once, and the whole performance collapses into ssh deploy.

The file, and the one rule nobody mentions

Section titled “The file, and the one rule nobody mentions”

~/.ssh/config is your personal config; /etc/ssh/ssh_config is the system-wide one, which you can leave alone for the whole of your natural life. If the file doesn’t exist, create it — and mind the permissions, because SSH quietly ignores anything it considers too permissive:

Terminal window
mkdir -p ~/.ssh && chmod 700 ~/.ssh
touch ~/.ssh/config && chmod 600 ~/.ssh/config

The format is simple enough: a Host pattern, followed by the options that apply to it:

Host deploy
HostName 203.0.113.10
User deploy

Now the rule that catches everyone: SSH uses the first value it finds for each option, not the last. Nearly every other config format on the planet lets later lines override earlier ones; SSH read that convention and calmly went the other way. So specific hosts go at the top of the file, and your Host * defaults go at the bottom — the catch-all, not the final word. If a setting ever seems to be ignored, this paragraph is why.

~/.ssh/config
# Specific hosts first
Host deploy
HostName 203.0.113.10
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519
Host db.internal
ProxyJump bastion.example.com
User ops
# Defaults for everything else — keep this at the bottom
Host *
AddKeysToAgent yes
UseKeychain yes # macOS only; delete on Linux
ServerAliveInterval 60
  • Host deploy is the nickname; everything beneath it is what the nickname means. ssh deploy, scp deploy:app.log . and git clone deploy:repo all work from it.
  • HostName accepts DNS names as well as IPs, and a name like box.example.com ages far better than an address you’ll have to update by hand.
  • ProxyJump hops through a bastion: connecting to db.internal silently tunnels via bastion.example.com. The one-off flag version is ssh -J bastion.example.com db.internal.
  • ServerAliveInterval 60 nudges the connection every minute, which stops NATs and hotel Wi-Fi dropping your session mid-deploy. They were going to try; now they can’t.
  • AddKeysToAgent yes loads keys into your agent the first time you use them, so you type each passphrase once per login rather than once per connection.

If every connection still asks for a password, fix that first — it takes two commands:

Terminal window
ssh-keygen -t ed25519 -C "[email protected]"
ssh-copy-id deploy

ssh-keygen makes you a modern Ed25519 keypair: accept the default location, and set a passphrase — you have a password manager, surely. ssh-copy-id appends the public half to the server’s authorized_keys, and it honours the config — deploy works here just as it does for ssh itself.

The passphrase is then the agent’s problem. With AddKeysToAgent yes — and UseKeychain yes on macOS, which files it in the Keychain — you unlock each key once and forget about it. On Linux, any desktop environment worth its window decorations already runs an agent; minimal setups can start one with eval "$(ssh-agent)".

The most common reason people meet this file: a work GitHub account and a personal one, each with its own key. The trick is two nicknames pointing at the same host:

Host github.com-work
HostName github.com
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
Host github.com
HostName github.com
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes

Clone work repos from [email protected]:org/repo.git and personal ones as normal; each goes out carrying the right identity.

IdentitiesOnly yes is the load-bearing line. Without it, SSH offers every key in your agent, one per attempt — and servers allow only a handful of attempts before losing patience. With a well-stocked keyring, GitHub pulls the plug with Too many authentication failures before the right key comes up.

One more trick, worth the three lines. SSH can keep a master connection open and run subsequent sessions over it:

Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 10m
Terminal window
mkdir -p ~/.ssh/sockets && chmod 700 ~/.ssh/sockets

The first connection to a host pays the full handshake; everything in the next ten minutes — ssh, scp, rsync, git pull — reuses it and lands essentially instantly. On slow links or far-away servers it feels like cheating, which is how you know it’s working.

  • ssh -G deploy prints the effective configuration for a host — every option, from every block, after all the matching has settled. The config file is write-many, read-never; this is how you find out what SSH actually thinks.
  • ssh -v deploy narrates the connection attempt line by line: which config file it read, which key it offered, where it gave up. -vv and -vvv increase the candour.

As the dotfiles guide insists: ~/.ssh/ never goes into a repository, private or otherwise. The config file itself holds no secrets, but it lives next to all of your keys, and the simplest safe habit is keeping the whole directory out of version control. If you want the config backed up, copy it into your dotfiles repo by hand and leave the keys behind.

The config gets you connected quickly; tmux keeps everything running when the connection drops anyway — between the two, that’s the whole remote-work stack. Beyond that: a terminal emulator worthy of the sessions you’re now enjoying, and the keyboard shortcuts to drive it all without touching the mouse.