Fixing SSH “Match” Blocks Breaking Host Aliases: A Subtle but Important Trick
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:
AMatchblock can silently deactivate yourIncludefile, 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 knownEven 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:
Hostblocks define aliases and connection defaults.Matchblocks conditionally apply settings based on User, Host, or other criteria.After SSH enters a
Matchblock, it stays in that conditional context
until it hits:- the next
Matchblock - or the next
Hostblock
- the next
- Anything inside a non-matching
Matchblock 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_hostsAnd inside ~/.ssh/project_hosts:
Host app-prod
HostName 192.0.2.1
Port 2222If you run:
ssh deploy@app-prodSSH will:
- Evaluate the
Matchrules. - If the
UserorHostpatterns do not match, - Then the
Includeline is treated as parse-only (ignored for actual config). - The alias
app-prodis never applied. - SSH tries to resolve
app-prodthrough DNS. - You get:
Could not resolve hostname app-prodEven 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_hostsNow 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:
Includeapplies for all hosts.Hostaliases inside the included file work.Matchrules 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
Matchblocks are powerful but have global effects that persist until terminated.- If you place an
Includeafter aMatch, 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.