The SSH config file, with examples

~/.ssh/config stores settings per host, so ssh web can replace a long command. ssh reads the file from top to bottom, and the first value found for each option wins, so put specific hosts first and Host * defaults last. Run ssh -G web to see the final settings without connecting.

Updated . Each example was run through ssh -G with OpenSSH 10.0 on Debian 13, using a real ~/.ssh/config in a test container.

A working example

Include ~/.ssh/config.d/*

Host web
  HostName 203.0.113.10
  User deploy
  Port 2222
  IdentityFile ~/.ssh/id_ed25519_work
  IdentitiesOnly yes

Host *.example.com
  User admin

Host *
  ServerAliveInterval 60
  User user

Now ssh web means ssh -p 2222 -i ~/.ssh/id_ed25519_work deploy@203.0.113.10, and it also sends a keepalive every 60 seconds. The same names work for scp, sftp and rsync.

The file is ~/.ssh/config. It must not be writable by others, or ssh stops with Bad owner or permissions. Run chmod 600 ~/.ssh/config. System-wide settings are read after it, from /etc/ssh/ssh_config.

With dozens of hosts, the file is only part of the answer. See how to manage many SSH servers for names, groups and keys that stay tidy.

The first value wins

This is the rule that surprises people. Every Host block that matches applies, not only the first one. For each option, ssh keeps the first value it reads and ignores later ones. In the example:

  • ssh web gets User deploy, because its block comes before Host *.
  • ssh db.example.com gets User admin.
  • ssh other gets User user, and all three get ServerAliveInterval 60.

So put specific hosts at the top and defaults at the end. If you move Host * to the top, its User wins everywhere. Options on the command line come first of all, so ssh -l other web, ssh other@web and ssh -p 3000 web override the file.

There is one exception worth knowing. IdentityFile adds up: a value in Host web-* and another in Host * are both tried, in order. Add IdentitiesOnly yes if you want to avoid keys from the agent, and leave IdentityFile out of Host * if you want one key per host.

Host patterns

PatternMatches
Host web dbEither name.
Host *.example.comAny name ending in .example.com.
Host web-?web- and one character.
Host web-* !web-oldAll web- names except web-old. A negation alone matches nothing: it only excludes from a positive match.

The pattern is compared with what you type, not with the address it resolves to. %h stands for that name, which makes short names possible:

Host web-*
  HostName %h.example.com

ssh web-1 now connects to web-1.example.com.

Include

Include ~/.ssh/config.d/*

This reads other files as if they were pasted in that place, which lets you keep one file per project. Relative paths start from ~/.ssh. It needs OpenSSH 7.3 or later.

Put it at the top of the file. A Host block continues until the next one, and an Include placed after it is part of that block. We tested this: with Include below a Host a block, the settings in the included file did nothing for any other host. You can use this on purpose, to include a file only for some hosts.

Match

Match is a more powerful form of Host, with conditions:

Match host "*.internal" user deploy
  Port 2200

Match exec "test -f /tmp/at-office"
  ProxyJump bastion

The first applies only when both conditions are true. The second runs a shell command and applies if it exits with 0, which is how you change settings by network or place. Note that this command runs each time the config is read, even with ssh -G.

The useful conditions are host, originalhost, user, localuser, exec, tagged, final and all. host checks the name after HostName is applied, and originalhost checks what you typed. final makes ssh read the configuration a second time, which lets a block see the result of the first pass:

Host *.corp
  HostName %h.example.com

Match final host *.example.com
  User admin

In OpenSSH 9.4 and later, you can also tag hosts, and select them with Match tagged or ssh -P:

Host web
  Tag prod

Match tagged prod
  User deploy

ProxyJump has its own page: how to use ProxyJump and a jump host in SSH.

See the result with ssh -G

ssh -G web

This prints every setting ssh would use for that host, with all files and rules applied, and exits without connecting. It is the fastest way to answer "why does it use that user?":

ssh -G web | grep -E '^(hostname|user|port|identityfile) '

To find out whether your config is the cause of a problem, ignore it for one command. -F none skips both the user file and the system file:

ssh -F none user@203.0.113.10

For the connection itself, ssh -v web prints which config lines were applied.

Common mistakes

  • A Host * at the top that overrides everything below it.
  • A global option at the end of the file. Options outside a block must come before the first Host. At the end, they belong to the last block.
  • A typo in an option. ssh stops with Bad configuration option and connects to nothing, on any host. For an option that only some versions know, put IgnoreUnknown and the option name above it.
  • Secrets in the file. It holds paths to keys, not keys, and it has no password option. That is how it should stay.

Questions

Where is the SSH config file?

On Linux and macOS it is ~/.ssh/config in your home folder. On Windows it is C:\Users\name\.ssh\config. It does not exist until you create it. System-wide settings in /etc/ssh/ssh_config are read after it.

Why does ssh ignore a setting in my config?

Another matching block set the same option earlier, and the first value wins. Run ssh -G followed by the host name to see the value ssh will use, and move specific hosts above Host *.

Does the SSH config file work with scp, sftp and rsync?

Yes. They use ssh to connect, so the host names and options in ~/.ssh/config apply to them too, for example scp file.txt web:/tmp/.

Sources

More in Configuration and many servers

From the blog

All guides