When configuring SSH, most developers know the basics— Host, HostName, IdentityFile, and so on. But once you start using Match blocks and Include statements in more complex setups (multiple keys, multiple users, multiple environments), you can easily run into a very subtle problem:

A Match block can silently deactivate your Include file, causing SSH to ignore host aliases.

This can lead to confusing errors such as:

ssh: Could not resolve hostname <alias>: nodename nor servname provided, or not known

Even though the alias is defined in an included file!

This post explains why this happens and the trick to avoid it.


How SSH Reads the Config File

OpenSSH processes ~/.ssh/config in order, top to bottom.

Key rules:

  1. Host blocks define aliases and connection defaults.
  2. Match blocks conditionally apply settings based on User, Host, or other criteria.
  3. After SSH enters a Match block, it stays in that conditional context
    until it hits:

    • the next Match block
    • or the next Host block
  4. Anything inside a non-matching Match block is ignored
    (SSH parses the lines for syntax but does not apply them).

This last behavior is where things get tricky.


The Problem: Include Inside a Failed Match Block

Consider a simplified config:

Match User deploy Host *prod*
  IdentityFile ~/.ssh/deploy-prod

Match User deploy Host *dev*
  IdentityFile ~/.ssh/deploy-dev

Include ~/.ssh/project_hosts

And inside ~/.ssh/project_hosts:

Host app-prod
  HostName 192.0.2.1
  Port 2222

If you run:

ssh deploy@app-prod

SSH will:

  1. Evaluate the Match rules.
  2. If the User or Host patterns do not match,
  3. Then the Include line is treated as parse-only (ignored for actual config).
  4. The alias app-prod is never applied.
  5. SSH tries to resolve app-prod through DNS.
  6. You get:
Could not resolve hostname app-prod

Even though the host is correctly defined!

This is an extremely common but rarely documented issue.


The Fix: Terminate the Match Section Before Include

You can explicitly end any active or inactive Match context by inserting:

Host *

This resets SSH back to normal processing.

Fixed config:

Match User deploy Host *prod*
  IdentityFile ~/.ssh/deploy-prod

Match User deploy Host *dev*
  IdentityFile ~/.ssh/deploy-dev

# ← Reset Match context here
Host *

# ← Now Include is unconditional again
Include ~/.ssh/project_hosts

Now the included aliases apply for all connections, regardless of which Match block matched earlier.


Why This Works

Host * is a regular host block, which automatically ends any previous Match context. After SSH encounters it, the remainder of the file is interpreted normally:

  • Include applies for all hosts.
  • Host aliases inside the included file work.
  • Match rules still apply correctly earlier.

This gives you the best of both worlds:

  • Conditional key selection using Match
  • Unconditional host definitions and aliases using Include

Without the reset, your Include may unintentionally remain inside a “failed” Match block, making its contents ignored.


Example Verification

You can confirm the behavior using:

ssh -G <alias> | grep -E '^(user|hostname|port|identityfile)'

If the alias maps correctly, you’ll see the expanded HostName and Port.
If not, SSH will show hostname <alias>—a sign that the alias isn’t being applied.


Takeaways

  • Match blocks are powerful but have global effects that persist until terminated.
  • If you place an Include after a Match, it may be ignored unless the match succeeds.
  • Use:
Host *

to reset the context before an Include.

This small trick prevents hours of debugging and keeps your SSH config modular and predictable.


If you maintain multi-user or multi-environment SSH configurations—with dedicated keys, agents, or auto-generated host lists—this technique is essential.

Tag:none

Add a new comment.