DevTools Hub

Search tools

Search for a developer tool

SSH Config File Guide

Part of the Encryption Toolkit

ssh user@203.0.113.5 -p 2222 -i ~/.ssh/id_work -J bastion.example.com is a lot to remember, let alone retype, for a server you connect to daily. Every one of those flags has a permanent home in ~/.ssh/config — write it once, and ssh myserver does the rest from then on. This guide covers how that file is actually structured, the precedence rules that trip people up, and the directives worth knowing beyond the handful everyone starts with.

Where the file lives, and what reads it

Your personal config is ~/.ssh/config — created by you, read only for your own connections. A separate system-wide file, /etc/ssh/ssh_config, applies to every user on the machine and is consulted after your personal one for anything it didn't already decide. Neither of these is sshd_config, a different file entirely that configures the SSH server daemon rather than the client — confusing the two is one of the most common places people get stuck searching for the wrong setting.

Precedence runs from most specific to least: flags given directly on the command line always win, then your personal ~/.ssh/config, then the system-wide file, then ssh's own built-in defaults for anything still unset. Nothing here requires a restart or a reload — ssh reads the config fresh at the start of every single connection, so editing the file takes effect on your very next command.

The anatomy of a Host block

A config file is a sequence of blocks, each starting with a Host line followed by indented directives that apply when that block matches:

Host myserver
    HostName 203.0.113.5
    User alice
    Port 2222

Host isn't necessarily a real address — it's a pattern matched against whatever you type after ssh. HostName is the directive that actually says where to connect; if you omit it, ssh falls back to using the Host value itself as the address, which is exactly why a block with only a Host line (and nothing else) is still valid syntax, just one where the alias and the real address happen to be the same string.

Patterns support the same wildcards as shell globbing — * for any sequence of characters, ? for a single one — so Host 10.0.0.* or Host staging-* matches a whole range of hosts with one block. A bare Host * matches everything and is commonly placed at the very end of a file to hold settings that should apply everywhere, unless a more specific block above already said otherwise.

Precedence: first match wins, except when it doesn't

This is the rule that actually explains most config surprises: for a given directive, ssh uses the first value it finds and ignores every later one for that same directive — which is why more specific blocks belong above more general ones, not below. Take two blocks that both match the same connection:

Host prod
    User deploy
    IdentityFile ~/.ssh/id_prod

Host *
    User fallback
    IdentityFile ~/.ssh/id_default
    Port 2222

Connecting to prod matches both blocks. User resolves to deploy — the first block's value wins, and the wildcard's fallback is never even considered. Port resolves to 2222 — the prod block never set it, so the later wildcard value is used since there was no earlier one to take precedence. IdentityFile is the genuine exception to all of this: it's one of a handful of directives (LocalForward, RemoteForward, SendEnv, and SetEnv are others) that accumulate across every matching block instead of stopping at the first — so this example ends up offering both id_prod and id_default, in that order.

The directives that come up constantly

  • HostName — the real address, when it differs from the alias in Host.
  • User — the remote username, so you don't retype alice@ every time.
  • Port — only needed away from the default of 22.
  • IdentityFile — which private key(s) to offer, tried in the order listed.
  • IdentitiesOnly — restricts ssh to only the keys named in IdentityFile, instead of also offering every key already loaded in your running agent. Worth setting explicitly once you have more than one or two keys around, since offering too many to a server can get your connection rejected outright before the right key is ever tried.
  • ProxyJump — routes the connection through an intermediate host, replacing the older, clunkier ProxyCommand + ssh -W pattern that did the same thing before OpenSSH 7.3 introduced this directive in 2016.
  • ForwardAgent — lets the remote host use your local agent's loaded keys to authenticate onward, without copying a private key there. See the security note below before turning this on by default anywhere.
  • ServerAliveInterval / ServerAliveCountMax — how often to ping an idle connection and how many missed pings to tolerate before giving up, useful for keeping a session alive through a NAT or firewall that silently drops quiet connections.
  • StrictHostKeyChecking — controls what happens when a host key doesn't match what's in known_hosts. Leave this at its secure default; setting it to no to silence a warning defeats the entire point of host key verification.

A realistic multi-host file

Putting several of these together the way a real file tends to look:

Host bastion
    HostName bastion.example.com
    User ops
    IdentityFile ~/.ssh/id_ed25519

Host internal-*
    ProxyJump bastion
    User deploy
    IdentityFile ~/.ssh/id_ed25519

Host github.com
    User git
    IdentityFile ~/.ssh/id_ed25519_github

Host *
    ServerAliveInterval 30
    ServerAliveCountMax 3
    IdentitiesOnly yes

Nothing here needs to be typed in that order by hand — a tool like SSH Config Generator builds one correctly indented block at a time from plain fields; stack a few of them and a trailing Host * to get a file that looks like this.

Match: conditional configuration beyond what Host can express

Host only ever matches against the alias you typed. Match, a separate and more powerful directive, can branch on the actual resolved hostname, your local username, which network interface you're connecting from, or the output of an arbitrary shell command — combined with AND logic across multiple conditions in one line:

Match host internal-db exec "ping -c1 -W1 10.0.0.1"
    ProxyJump bastion

That example only adds the jump host when both the hostname matches and theexec command succeeds — useful for a laptop that needs a bastion when off the office network but can reach the same server directly when on it. Most configs never need Match at all; reach for it specifically when a rule depends on something Host's pattern-only matching genuinely can't express, rather than as a default starting point.

Speeding up repeated connections with multiplexing

Every new SSH connection re-does the full handshake and authentication from scratch, which is noticeable if you open several connections to the same server in a short span — several terminal tabs, or a script that shells out to the same host repeatedly. Connection multiplexing reuses one already-authenticated connection for everything after the first:

Host *
    ControlMaster auto
    ControlPath ~/.ssh/sockets/%r@%h-%p
    ControlPersist 600

The first connection to a given host opens a control socket at the path named by ControlPath; every subsequent connection to that same host detects the socket and rides on top of it instead of authenticating again, which is why the second and laterssh or scp command to the same server often returns almost instantly. ControlPersist 600 keeps that socket alive for 10 minutes after the last session using it closes, so a quick round of separate commands doesn't each pay the full connection cost. The one setup step this needs: the directory in ControlPath has to already exist, since ssh won't create it for you.

Debugging a config that isn't doing what you expect

Two commands answer almost every "why isn't this working" question without guessing:

ssh -G myserver   # print the fully resolved configuration for "myserver" and exit — no connection attempted
ssh -v myserver   # verbose connection log — shows which config file and key actually got used

ssh -G is the more useful of the two for config problems specifically: it shows exactly what ssh would use for every directive after merging every matching block, which immediately reveals whether a value you expected got overridden by an earlier, more general block.

Common mistakes

  • Putting a wildcard block before the specific ones. Since the first matching value wins, a Host * block placed at the top of the file can silently shadow settings a more specific block further down was trying to set — keep general fallback blocks at the bottom, not the top.
  • Expecting IdentityFile to behave like every other directive. It's one of the few that accumulate instead of stopping at the first match, so a key listed in an earlier specific block and a later wildcard block both get offered, not just the first one — occasionally surprising when debugging why a server saw more keys than expected.
  • Not noticing a typo breaks every connection, not just one setting. OpenSSH rejects an unrecognized directive outright — Bad configuration option: usre for a misspelled User, say — and refuses to connect at all until it's fixed, even for hosts whose blocks never touched the broken line.ssh -G host after any edit confirms the file still parses and the value you changed actually took effect.

Security considerations

  • ForwardAgent is a trust decision, not a convenience toggle. Anyone with root on a host you've enabled it for can use your agent's loaded keys for as long as your session stays open. Scope it to specific trusted hosts, never to Host *.
  • Never lower StrictHostKeyChecking to silence a warning you don't understand. That warning exists specifically to catch a host key that changed unexpectedly — the exact signature of a machine-in-the-middle or a genuinely re-provisioned server, and you want to know which before continuing either way.
  • IdentitiesOnly avoids leaking which keys you hold. Without it, a server can see every key your agent offers during authentication, not just the one that eventually succeeds — mildly informative to an attacker even when authentication itself fails.

FAQ

What's the difference between ssh_config and sshd_config?

ssh_config (~/.ssh/config or /etc/ssh/ssh_config) configures the client — your behavior when you run ssh. sshd_config (/etc/ssh/sshd_config) configures the server daemon — what it accepts from connecting clients. They live on different machines conceptually even when both happen to be installed on the same one, and editing one has no effect on the other.

Does the order of Host blocks in the file matter?

Yes, for most settings — OpenSSH uses the first matching value it finds for a given keyword and ignores later ones for that same keyword, so more specific Host blocks should come before general or wildcard ones. A few keywords, IdentityFile among them, are the exception and accumulate across every matching block instead of stopping at the first.

What happens when two Host patterns both match the same alias?

Both blocks apply, merged together — this is how a specific Host block combined with a trailing "Host *" catch-all works in practice. For any single keyword set in both, the block that appears first in the file wins; keywords only set in the second block still take effect since there was no earlier value to conflict with.

Do I need to restart ssh or reload anything after editing this file?

No — ssh reads ~/.ssh/config fresh at the start of every connection. Save the file and your very next ssh command already sees the change.

What's the difference between IdentityFile and IdentitiesOnly?

IdentityFile lists which private keys to offer. Without IdentitiesOnly, ssh may also offer additional keys already loaded in your running ssh-agent, which can cause a server to reject the connection after too many failed attempts if several unrelated keys are loaded. Setting "IdentitiesOnly yes" restricts ssh to only the IdentityFile entries you explicitly listed.

Can Match do more than Host can?

Yes — Match can branch on more than just the alias: the actual resolved hostname, your local username, the network you're connecting from, and more, combined with AND logic. Host is enough for the vast majority of real configs; Match exists for the more conditional cases Host's pattern-only matching can't express.

Try it yourself

SSH Config Generator builds a complete, correctly indented Host block from plain fields — Host, User, Port, IdentityFile, ProxyJump, and ForwardAgent — ready to copy or download straight into ~/.ssh/config. For how the key authentication underneath all of this actually works, see SSH Keys Explained.

Related tools