Sorting out the ordering of OpenSSH configuration directives

April 6, 2025

As I discovered recently, OpenSSH makes some unusual choices for the ordering of configuration directives in its configuration files, both sshd_config and ssh_config (and files they include). Today I want to write down what I know about the result (which is partly things I've learned researching this entry).

For sshd_config, the situation is relatively straightforward. There are what we could call 'global options' (things you set normally, outside of 'Match' blocks) and 'matching Match options' (things set in Match blocks that actually matched). Both of them are 'first mention wins', but Match options take priority over global options regardless of where the Match option block is in the (aggregate) configuration file. Sshd makes 'first mention win' work in the presence of including files from /etc/ssh/sshd_config.d/ by doing the inclusion at the start of /etc/ssh/sshd_config.

So here's an example with a Match statement:

PasswordAuthentication no
Match Address 127.0.0.0/8,192.168.0.0/16
  PasswordAuthentication yes

Password authentication is turned off as a global option but then overridden in the address-based Match block to enable it for connections from the local network. If we had a (Unix) group for logins that we wanted to never use passwords even if they were coming from the local network, I believe that we would have to write it like this, which looks somewhat odd:

PasswordAuthentication no
Match Group neverpassword
 PasswordAuthentication no
Match Address 127.0.0.0/8,192.168.0.0/16
  PasswordAuthentication yes

Then a 'neverpassword' person logging in from the local network would match both Match blocks, and the first block (the group block) would have 'PasswordAuthentication no' win over the second block's 'PasswordAuthentication yes'. Equivalently, you could put the global 'PasswordAuthentication no' after both Match blocks, which might be clearer.

The situation with ssh and ssh_config is one that I find more confusing and harder to follow. The ssh_config manual page says:

Unless noted otherwise, for each parameter, the first obtained value will be used.

It's pretty clear how this works for the various sources of configurations; options on the command line take priority over everything else, and ~/.ssh/config options take priority over the global options from /etc/ssh/ssh_config and its included files. But within a file (such as ~/.ssh/config), I get a little confused.

What I believe this means for any specific option that you want to give a default value to for all hosts but then override for specific hosts is that you must put your Host * directive for it at the end of your configuration file, and the more specific Host or Match directives first. I'm not sure how this works for matches like 'Match canonical' or 'Match final' that happen 'late' in the processing of your configuration; the natural reading would be that you have to make sure that nothing earlier conflicts with them. If this is so, a natural use for 'Match final' would then be options that you want to be true defaults that only apply if nothing has overridden them.

Some ssh_config options are special in that you can provide them multiple times and they'll be merged together; one example is IdentityFile. I think this applies even across multiple Host and Match blocks, and also that there's no way to remove an IdentityFile once you've added it (which might be an issue if you have a lot of identity files, because SSH servers only let you offer so many). Some options let you modify the default state to, for example, add a non-default key exchange algorithm; I haven't tested to see if you can do this multiple times in Host blocks or if you can only do it once.

(These days you can make things somewhat simpler with 'Match tagged ...' and 'Tag'; one handy and clear explanation of what you can do with this is OpenSSH Config Tags How To.)

Typically your /etc/ssh/ssh_config has no active options set in it and includes /etc/ssh/ssh_config.d/* at the end. On Debian-derived systems, it does have some options specified (for 'Host *', ie making them defaults), but the inclusion of /etc/ssh/ssh_config.d/* has been moved to the start so you can override them.

My own personal ~/.ssh/config setup starts with a 'Host *' block, but as far as I can tell I don't try to override any of its settings later in more specific Host blocks. I do have a final 'Host *' block with comments about how I want to do some things by default if they haven't been set earlier, along with comments in the file that I was finding all of this confusing. I may at some point try to redo it into a 'Match tagged' / 'Tag' form to see if that makes it clearer.


Comments on this page:

By John Morton at 2025-04-12 00:41:02:

I made an Include based set of ssh_config files for my team last year and wrote this in the README:

However, there are a few additional subtleties:

  1. Declarations that are not in the context of a previous Host or Match appear to be functionally equivalent to ones made after a Host * declaration.
  2. Host or Match scopes end at the end of the file they're in, which includes both the standard configuration files and any top level Include fragment; any bare declaration that effectively follows them in the next file is actually in a tacit Host * block.
  3. However, Include fragments inside the context of a Host or Match scope have bare directives only applied to the enclosing scope. Starting a new scope inside one of these *Include fragments appears to never be applied.

...however I'm not entirely sure viewing bare declarations as being in a tacit Host * block is necessarily true, based on what you've written. Of course, the built in default might be complicating matters. I think I'm going to have to spend more time figuring it out :-/

In addition to IdentityFile, CertificateFile doesn't hold to the first use wins model, and the same is true for SendEnv. Apparently SetEnv unintentionally behaved last use wins until fixed in 9.1.

Perhaps the most annoying first use wins property is that it applies to the sundry algorithm selection directives, even when they use the suffixes to manage append, prepend and removal from the default lists. It makes them especially frustrating to use in a ...heterogeneous environment.

Written on 06 April 2025.
« My pessimism about changes to error handling in Go (but they'll happen)
Fedora 41 seems to have dropped an old XFT font 'property' »

Page tools: View Source, View Normal.
Search:
Login: Password:

Last modified: Sun Apr 6 22:50:29 2025
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.