Skip to content

SSH Config: 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 refuses to connect at all if anyone but you can write to it — a curt Bad owner or permissions on ~/.ssh/config and nothing else. Merely readable it will tolerate; writable it will not:

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.

That settles which account pushes. Which email the commits are written by is git’s business rather than SSH’s, and a conditional include in your git config sets it per directory, so work repositories stop collecting your personal address.

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.
  • A shell that connects fine but cannot clear its own screen, or draws htop as abstract art, is not an SSH fault at all: your TERM went across and the remote host has never heard of it. Terminal colours and terminfo has the one-line fix.
  • Copying in Neovim or tmux on the server lands nowhere near your own clipboard, because the remote program can’t reach it. OSC 52 is the way across: the program asks your terminal to do the copying. Most terminals accept it out of the box, but GNOME Terminal and Ptyxis don’t support it and iTerm2 wants it switching on; the feature matrices have the full list for macOS and Linux.

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, and the tmux cheat sheet is the half of it you will actually have to look up. tmux rather than anything newer, for one unglamorous reason: it is already installed on the far end, which is where the argument against its rivals lives. 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.