New troubleshooting/networking article covering the three SSH failure modes after a fleet migration (stale hardcoded IP, Tailscale 1.98.x cold-path teardown, rebuilt-box host-key mismatch) and the durable fix (MagicDNS names + known_hosts purge + ConnectTimeout), with the WSL2 no-resolver caveat. Cross-links the existing host-key article (adds a 'when pinning the IP is wrong' callout) and adds the SUMMARY nav entry.
4.9 KiB
| title | domain | category | tags | status | created | updated | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| SSH Alias Falls Through to MagicDNS — Host-Key Verification Failure (No `Host` Block) | selfhosting | troubleshooting |
|
published | 2026-06-11 | 2026-06-12 |
SSH Alias Falls Through to MagicDNS — Host-Key Verification Failure (No Host Block)
The Problem
You ssh to a host you've reached many times before, but now it dies before any
auth happens:
$ ssh MyMac
ssh_askpass: exec(/usr/libexec/openssh/ssh-askpass): No such file or directory
Host key verification failed.
On a headless box (WSL, a server, a CI runner) there's no askpass binary, so the prompt can't even be shown — SSH just aborts. Connecting by Tailscale IP works fine:
$ ssh user@100.74.124.81 # works
$ ssh MyMac # Host key verification failed
Why It Happens
There is no Host MyMac block in ~/.ssh/config at all — and there never was.
The connection only ever worked by IP, or interactively (where you clicked through
the first-connect yes prompt without noticing).
When no Host block matches, SSH uses the literal argument as the hostname. With
Tailscale MagicDNS, MyMac (or mymac) resolves to the node — so the connection
succeeds — but the host key it presents is checked against known_hosts under the
name mymac, which has no entry. Meanwhile the key you actually trust is stored
under the IP:
$ ssh-keygen -F 100.74.124.81 # found — line 67
$ ssh-keygen -F mymac # nothing
So strict host-key checking has nothing to match, tries to prompt to accept the
"new" key, and on a headless host that prompt fails → Host key verification failed.
Confirm there's no block (and that ssh -G is just echoing defaults):
$ ssh -G MyMac | grep -E '^(hostname|user|port) '
hostname mymac # lowercased literal — NOT an explicit HostName
user youruser # your local username default — not from a block
port 22 # default
If hostname equals the arg you typed (just lowercased) and user is your local
login name, there is no matching Host block.
The Fix
Add an explicit Host block that pins the IP that known_hosts already trusts.
This matches the convention every other host in a Tailscale fleet should follow —
pin the 100.x address, not the MagicDNS name:
Host MyMac mymac
HostName 100.74.124.81
User youruser
IdentityFile ~/.ssh/id_ed25519
[!note] When pinning the IP is the wrong call Pinning the IP is right while the host is stable. If the box gets migrated or rebuilt — new Tailscale IP and new host key — the pin rots and
known_hostsmismatches. At that point switch to MagicDNS names so the alias self-heals. See MagicDNS Names vs Pinned IPs for Tailscale SSH (After a Fleet Migration).
Now ssh MyMac resolves to 100.74.124.81, whose key is in known_hosts, and the
check passes with no prompt. Verify non-interactively:
$ ssh -o BatchMode=yes MyMac 'hostname'
mymac.majorlan
BatchMode=yes disables every prompt — if it returns the hostname cleanly, the key
is trusted and a real key authenticated.
Don't over-pin the identity. Run ssh -v user@<IP> true and check the
Will attempt key / accepted-key lines first. A workstation often authenticates
with the default id_ed25519, not a fleet key — if id_ed25519_fleet isn't even
offered, don't put it in the block.
Cleanup: Stale known_hosts Cruft
Drive-by ssh attempts leave junk entries like mymac-2 (auto-suffixed names from
old keys). They never match anything once you pin the IP. Purge them:
$ ssh-keygen -R mymac-2
How to Diagnose This
ssh -o BatchMode=yes <alias> true— if it fails withHost key verification failed(notPermission denied), it's a host-key problem, not auth.ssh -G <alias> | grep -E '^(hostname|user|port) '— ifhostnameis just your typed arg and there's no realHostName, there's noHostblock.ssh-keygen -F <name>vsssh-keygen -F <ip>— find which name actually holds the trusted key. Pin whichever oneknown_hostshas (usually the IP).
Why This Gotcha Is Invisible
It only surfaces on a host with no askpass (headless / WSL / cron). On a desktop,
the first-connect prompt appears, you hit yes, an entry gets written under the
MagicDNS name, and it "just works" — masking the fact that no Host block exists and
the IP-keyed entry is the only durable trust. Move the same config to a headless box
and the missing block becomes a hard failure. Related: SSH only applies Host blocks
by literal pattern match, so connecting by IP also skips them — see Ansible Fails
with Permission Denied While ssh <alias> Works (Host Alias Bypass).