Every time you type a long SSH command, you're doing work the SSH client is happy to do for you. The file ~/.ssh/config is a small, plain-text file that remembers your connection details — hostnames, usernames, ports, keys — so a sprawling one-liner collapses into a short alias like ssh work. This article covers the problem the file solves, how it works, and the eight directives you'll use every day.

The Problem

Consider a realistic command to reach a work server that lives on a non-standard port, uses a dedicated key, and expects a specific username:

bash — a typical long command
alice@laptop:~$ ssh -i ~/.ssh/id_work_ed25519 -p 2222 alice@long.hostname.example.com

There is nothing wrong with this command — it works. The problem is everything around it. You have to remember the exact key path, the port number, the username, and the fully qualified hostname. Miss one flag and the connection fails with a confusing error. Multiply that by ten servers, three keys, and a handful of usernames, and the mental bookkeeping becomes unmanageable.

People cope in ways that make things worse: they memorize the command wrong, they create shell aliases that only work in their own terminal and break for tools like scp and Git, or they hard-code the command into scripts that fail silently months later when a hostname changes. The SSH client already knows how to read a per-user configuration file and apply those settings automatically — you describe each host once and from then on you connect by name.

BEFORE ssh -i ~/.ssh/id_work_ed25519 -p 2222 alice@work-server.example.com ~/.ssh/config remembers the details AFTER ssh work
The long, flag-heavy command collapses into a two-word alias once the details live in the config file.

The File Itself

The per-user configuration file is ~/.ssh/config — a file literally named config with no extension, inside the hidden .ssh directory in your home folder. The file does not need to exist before you use SSH. If it is absent, the client simply falls back to its built-in defaults. You create ~/.ssh/config only when you want to override those defaults.

The conventional, conservative permission is 600 (owner read/write, nobody else), which keeps the file's contents private and satisfies every SSH client's expectations.

bash — create and lock down the file
alice@laptop:~$ mkdir -p ~/.ssh alice@laptop:~$ touch ~/.ssh/config alice@laptop:~$ chmod 600 ~/.ssh/config

How SSH reads the file: first match wins

The client reads the file from top to bottom and keeps the first value it finds for each setting. This has one practical consequence: specific host blocks go at the top, general defaults at the bottom. A catch-all Host * block placed at the top of the file would shadow everything below it.

ssh work → parse top-to-bottom, first value wins Host work ← matches! read its settings Host db (skipped — no match) Host * ← fills in anything not set yet specific blocks first, Host * defaults last
The client keeps the first value it encounters for each setting. Specific host blocks go at the top; the catch-all Host * block at the bottom.

The Everyday Directives

Each directive below is explained the same way: what it does, what happens if you don't set it, and when you'd use it. Keywords are case-insensitive; argument values are case-sensitive.

1. Host — the block header Structural

DEFAULTNone — this is required to open a block.

Host begins a block. Everything indented below it, until the next Host line, applies only to hosts that match the pattern you write here. The pattern is the name you type after ssh — it's a label, not necessarily a real hostname. You can list several patterns on one line, and a single * means "all hosts" (useful for global defaults, at the bottom of the file).

~/.ssh/config
Host work # settings for the "work" alias go here

2. HostName — the real address Convenience

DEFAULTWhatever name you typed after ssh on the command line.

HostName is the actual DNS name or IP address the alias resolves to. This is what makes ssh work actually reach work-server.example.com.

~/.ssh/config
Host work HostName work-server.example.com

3. User — the login name Convenience

DEFAULTYour local username on the machine running the SSH command.

Sets the username to authenticate as on the remote host. Without it, SSH assumes the remote username matches your local one — often wrong on servers where you may be alice locally but deploy or ubuntu on the server.

~/.ssh/config
Host work HostName work-server.example.com User alice

4. Port — a non-default port Convenience

DEFAULT22 — the standard SSH port.

Sets the TCP port to connect to. The config remembers it for ssh, but also for scp, sftp, and Git-over-SSH — sparing you the inconsistent port flags those tools use (-p versus -P).

A note on changing the SSH port

Moving SSH off port 22 doesn't make a server meaningfully more secure — a port scan finds the service in seconds. Its real benefit is log hygiene: most automated brute-force bots only probe port 22, so relocating the service reduces failed-login noise in the logs. Real hardening comes from key-only authentication, not the port number.

~/.ssh/config
Host work HostName work-server.example.com Port 2222

5. IdentityFile — which private key to use Recommended

DEFAULTThe client looks for standard key filenames in ~/.ssh/: id_ed25519, id_rsa, and a few others.

Points at the specific private key for this host. You need it when you keep more than one key and want a given host to use a specific one. Also required whenever your keys use non-standard filenames, since SSH won't discover those by default.

Important quirk: on its own, IdentityFile only adds a preferred key to the list. It doesn't stop the client from also trying every other key your agent holds — which is what the next directive fixes.

~/.ssh/config
Host work IdentityFile ~/.ssh/id_work_ed25519

6. IdentitiesOnly — the critical companion Critical

DEFAULTno — meaning the client offers every key it can find, one by one, until one is accepted.

Setting IdentitiesOnly yes tells the client to offer only the specific key you named with IdentityFile, and nothing else — even if your agent has other keys loaded.

Why does that matter? Servers only allow a small number of authentication attempts per connection — typically six — before hanging up. Each key your client offers counts as one attempt. If you hold seven keys, the server disconnects before the right one is even tried, and you see the infamous error:

bash — the notorious error
Received disconnect from ... port 22:2: Too many authentication failures

There's also a privacy angle: offering every public key you hold to a server tells whoever runs it the full list of identities you control. Restricting the offer is both a reliability fix and an information-leak fix.

Best practice

Put IdentitiesOnly yes once in a Host * block at the bottom of your file, then give each host its own IdentityFile. You get correct behaviour everywhere without repeating yourself.

~/.ssh/config
Host work IdentityFile ~/.ssh/id_work_ed25519 IdentitiesOnly yes

7. AddKeysToAgent — load keys on first use Recommended

DEFAULTno — you have to run ssh-add manually to load a key into your agent.

When set to yes, the SSH client loads your key into the running agent the first time it's used. You type the the passphrase once, and every subsequent connection uses the cached key silently. This removes the friction that tempts people to strip passphrases off their keys — which is much worse for security.

Other values are available: ask prompts before loading, confirm requires approval on each use, and a time interval like 1h sets an expiry. Requires a running agent — silently ignored otherwise.

~/.ssh/config
Host * AddKeysToAgent yes

8. UseKeychain — store the passphrase (macOS only) macOS

DEFAULTno. Also: this option only exists in Apple's version of SSH — not in mainline OpenSSH.

On macOS, UseKeychain yes tells the SSH client to fetch (and store) your key's passphrase from the system Keychain. You never retype it — the key becomes usable as soon as you log into your Mac account.

Because this is an Apple-only extension, a non-Apple ssh (for example one installed via Homebrew) may reject it with an error. Guard it with IgnoreUnknown so a foreign client skips the line instead of failing.

~/.ssh/config
Host * IgnoreUnknown UseKeychain UseKeychain yes AddKeysToAgent yes

A Practical Comparison

Meet Alice and Bob. Both have the same setup: two SSH keys on their laptop and two servers they connect to regularly. But each has written their ~/.ssh/config differently — and the consequences show up quickly.

Alice and Bob's shared setup

Keys on the laptop: ~/.ssh/id_ed25519_personal and ~/.ssh/id_ed25519_work — both already loaded into the running ssh-agent. Note the non-standard filenames: SSH won't discover these on its own.
Servers: personal.example.com (default port) and work-server.example.com on port 2222.

Bob wrote something minimal that "just works" today. Alice followed the pattern this article recommends. Here are the two files side by side:

✗ BOB — NOT IDEAL
Host personal HostName personal.example.com Host work HostName work-server.example.com Port 2222
✓ ALICE — RECOMMENDED
Host personal HostName personal.example.com IdentityFile ~/.ssh/id_ed25519_personal Host work HostName work-server.example.com Port 2222 IdentityFile ~/.ssh/id_ed25519_work Host * IdentitiesOnly yes AddKeysToAgent yes

Now watch what happens when each of them runs ssh work:

Bob's config in action $ ssh work Laptop 🔑 personal 🔑 work personal key work key agent offers every loaded key Work server ✗ rejects wrong ✓ accepts right ✗ Works today, fragile tomorrow Personal key offered to the work server (privacy leak — server saw both public keys) Once loaded keys exceed the server's limit, it fails with "Too many authentication failures" Alice's config in action $ ssh work Laptop 🔑 personal 🔑 work work key only personal key stays home Work server ✓ accepts ✓ Clean, private, scales up Only the matching key was offered Personal key never leaves the laptop Behaviour stays correct as more keys are added
Same laptop, same agent-loaded keys, same servers — but Bob's config offers both keys to the work server, while Alice's offers only the right one.

What Alice did that Bob didn't

Just three things:

  1. Named a specific key per host with IdentityFile. Bob names no key at all, so his client offers every identity his agent has loaded. That works today with two keys — but both get presented to both servers, the personal key is needlessly exposed, and once the number of loaded keys grows past the server's attempt limit, connections start failing.
  2. Added IdentitiesOnly yes in a global Host * block. Without this, even with an IdentityFile, SSH still helpfully offers every other key it can find. Alice's config guarantees that only the named key is presented to each server.
  3. Enabled AddKeysToAgent yes globally. Alice types her passphrase once per session. Bob types his every time — or, more likely, gives up and removes the passphrase entirely, which is a much worse security outcome.

Bob's config isn't broken. It works right now with his current two keys. But it's fragile: it will misbehave once his key collection grows, it leaks more information than necessary, and it fights him on convenience. Alice's config — only three extra lines and one extra block — doesn't have any of those problems.

Defaults at a Glance

Directive Default when unset Purpose
Host None (required) Opens a block of settings for a named alias
HostName Name typed on command line The real address the alias points to
User Your local username Remote account to log in as
Port 22 TCP port on the server
IdentityFile Standard names in ~/.ssh/ Which private key to present
IdentitiesOnly no (offers every key) Restricts the client to the named key
AddKeysToAgent no Auto-loads keys into ssh-agent on first use
UseKeychain no (macOS only) Uses macOS Keychain to store passphrases

A Minimal Starter Config

A small, safe configuration you can drop into ~/.ssh/config to try the alias workflow. The Host * block goes at the bottom because of the first-match-wins rule.

~/.ssh/config — starter
# Specific hosts at the top Host work HostName work-server.example.com User alice Port 2222 IdentityFile ~/.ssh/id_work_ed25519 # Global defaults at the bottom Host * IdentitiesOnly yes AddKeysToAgent yes

With that file in place, ssh work does the same thing as the long command from the beginning of the article. Every other tool that uses SSH — scp, sftp, rsync, Git — reads the same config, so scp file.txt work:/tmp/ just works too.

Main References

  1. ssh_config(5) — OpenBSD manual pagesman.openbsd.org/ssh_config
  2. ssh(1) — OpenBSD manual pagesman.openbsd.org/ssh.1
  3. ssh-agent(1) — OpenBSD manual pagesman.openbsd.org/ssh-agent.1
  4. Mozilla InfoSec — OpenSSH Guidelinesinfosec.mozilla.org/guidelines/openssh
  5. Apple Developer Documentation — Using the macOS Keychain with SSHdeveloper.apple.com/library/archive/technotes/tn2449
← Back to all articles