If you juggle more than one SSH key — a personal GitHub account and a work one, a few servers, a deploy key — you’ve probably been retyping -i ~/.ssh/whatever on every command, or worse, hitting “Too many authentication failures” because SSH is throwing every key you own at the server until it gets locked out. The fix is a single file: ~/.ssh/config. This guide shows you how to map multiple SSH keys to multiple hosts cleanly, including the tricky case of two GitHub or Bitbucket accounts on one machine.
Why you need ~/.ssh/config
By default, when you run ssh user@host, the SSH client offers every private key it can find (and every key loaded in your agent) one after another. Two problems follow:
- Lockouts. Most servers cap authentication attempts at around six. If your agent holds more keys than that, the right key may never get tried — SSH gives up first with “Too many authentication failures.”
- Wrong identity. With two GitHub accounts, the first key offered wins, so you push to the wrong account or get “permission denied.”
A config file solves both by telling SSH exactly which key to use for which host — and nothing else.
The basic structure
Create (or edit) ~/.ssh/config and set its permissions so SSH trusts it:
touch ~/.ssh/config
chmod 600 ~/.ssh/config
The file is a list of Host blocks. Each block defines an alias and the settings SSH should use when you connect to it:
Host my-vps
HostName 203.0.113.10
User deploy
Port 22
IdentityFile ~/.ssh/vps_ed25519
IdentitiesOnly yes
Now ssh my-vps expands to “connect to 203.0.113.10 as deploy on port 22 using only the vps_ed25519 key.” No flags, no guessing.
The two directives that matter most
| Directive | What it does |
|---|---|
IdentityFile | The private key to use for this host. |
IdentitiesOnly yes | The key to avoiding lockouts. Tells SSH to offer only the IdentityFile above and ignore every other key in the agent. Always set this when you have multiple keys. |
Multiple keys for multiple servers
Just add one block per server, each pointing at its own key:
Host prod
HostName 203.0.113.10
User deploy
IdentityFile ~/.ssh/prod_ed25519
IdentitiesOnly yes
Host staging
HostName 203.0.113.20
User deploy
IdentityFile ~/.ssh/staging_ed25519
IdentitiesOnly yes
# Shared hosting on a non-standard port
Host hostinger
HostName 203.0.113.30
User u123456789
Port 65002
IdentityFile ~/.ssh/hostinger_ed25519
IdentitiesOnly yes
Then ssh prod, ssh staging, or ssh hostinger each use the correct key, user, and port automatically. The same aliases work with scp and rsync too, because they read the same config.
Two GitHub (or Bitbucket) accounts on one machine
This is the classic case. Both accounts live at the same real host (github.com), so you can’t tell them apart by hostname alone. The trick is to invent a fake Host alias for each account and map it back to the real host:
Host github-personal
HostName github.com
User git
IdentityFile ~/.ssh/personal_ed25519
IdentitiesOnly yes
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/work_ed25519
IdentitiesOnly yes
Now you choose the account by which alias you use in the remote URL. For a work repo, set the remote like this:
# Clone using the alias instead of github.com
git clone git@github-work:acme/internal-app.git
# Or fix an existing repo's remote
git remote set-url origin git@github-work:acme/internal-app.git
Git connects to github-work, SSH rewrites that to github.com with the work key, and GitHub authenticates you as the right account. Verify each alias independently:
ssh -T git@github-personal
ssh -T git@github-work
Helpful global defaults
A Host * block at the bottom of the file applies sensible defaults to every connection (specific blocks above it still win):
Host *
AddKeysToAgent yes
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes— loads a key into the agent the first time you use it, so you only enter its passphrase once.ServerAliveInterval/ServerAliveCountMax— send a keep-alive every 60 seconds and drop the connection after three misses, preventing “broken pipe” timeouts on idle sessions.- On macOS, add
UseKeychain yesinside this block to pull passphrases from the Keychain.
Troubleshooting
- “Too many authentication failures.” You’re missing
IdentitiesOnly yeson the host block, so the agent is offering every key. Add it. - Wrong GitHub account is used. Your repo’s remote still points at
github.cominstead of your alias. Rungit remote -vand update it withgit remote set-url. - Config seems ignored. Permissions are wrong (
chmod 600 ~/.ssh/config), or the file is in the wrong place — it must be~/.ssh/config. Debug withssh -v my-hostto see which config and key are loaded. - Order matters. SSH applies the first matching value for each setting, so put specific
Hostblocks above broadHost *wildcards.
New to keys altogether? Start with our guide on how to generate an SSH key, and see copying files with SCP — both honor the aliases you define here.
Frequently asked questions
How do I use a different SSH key for each host?
Add a Host block per server in ~/.ssh/config with its own IdentityFile and IdentitiesOnly yes. Then connect with the alias, e.g. ssh prod, and SSH uses that host’s key automatically.
How do I use two GitHub accounts with different SSH keys?
Create two Host aliases (e.g. github-personal and github-work) that both set HostName github.com but different IdentityFile values. Then set each repo’s remote to use the matching alias, e.g. git@github-work:org/repo.git.
What does IdentitiesOnly yes do?
It forces SSH to offer only the key named in that host’s IdentityFile, instead of every key in your agent. This prevents the “Too many authentication failures” lockout when you have several keys.
