From 9b066d0e543482e37d71b861cefe61d0a040bd48 Mon Sep 17 00:00:00 2001 From: MajorLinux Date: Thu, 11 Jun 2026 20:12:22 -0400 Subject: [PATCH] Add troubleshooting article: SSH alias MagicDNS fall-through host-key failure New 05-troubleshooting/networking article covering the case where ssh fails host-key verification because no Host block exists and the alias resolves via Tailscale MagicDNS to a name with no known_hosts entry (key stored under the IP). Registered in SUMMARY.md and the troubleshooting index. --- 05-troubleshooting/index.md | 1 + ...ng-host-block-magicdns-host-key-failure.md | 127 ++++++++++++++++++ SUMMARY.md | 1 + 3 files changed, 129 insertions(+) create mode 100644 05-troubleshooting/networking/ssh-missing-host-block-magicdns-host-key-failure.md diff --git a/05-troubleshooting/index.md b/05-troubleshooting/index.md index 1969a4d..f700612 100644 --- a/05-troubleshooting/index.md +++ b/05-troubleshooting/index.md @@ -18,6 +18,7 @@ Practical fixes for common Linux, networking, and application problems. - [Postfix header_checks Can't Act on Milter-Added Headers (Use Sieve)](networking/postfix-header-checks-vs-milter-headers.md) - [Dovecot Phantom Mailboxes from .dovecot.lda-dupes (mail_home Overlapping the Maildir Root)](networking/dovecot-mail-home-maildir-root-phantom-mailboxes.md) - [Tailscale SSH: Unexpected Re-Authentication Prompt](networking/tailscale-ssh-reauth-prompt.md) +- [SSH Alias Falls Through to MagicDNS — Host-Key Verification Failure (No `Host` Block)](networking/ssh-missing-host-block-magicdns-host-key-failure.md) - [iOS Tailscale Clients Report HostName="localhost" — Breaks /etc/hosts Generators](networking/tailscale-status-json-hostname-localhost-ios.md) - [rsync over Tailscale: Hung in TCP Teardown After Transfer Completes](networking/rsync-tailscale-teardown-stall.md) - [Windows OpenSSH: WSL Default Shell Breaks Remote Commands](networking/windows-openssh-wsl-default-shell-breaks-remote-commands.md) diff --git a/05-troubleshooting/networking/ssh-missing-host-block-magicdns-host-key-failure.md b/05-troubleshooting/networking/ssh-missing-host-block-magicdns-host-key-failure.md new file mode 100644 index 0000000..a87815f --- /dev/null +++ b/05-troubleshooting/networking/ssh-missing-host-block-magicdns-host-key-failure.md @@ -0,0 +1,127 @@ +--- +title: "SSH Alias Falls Through to MagicDNS — Host-Key Verification Failure (No `Host` Block)" +domain: selfhosting +category: troubleshooting +tags: + - ssh + - ssh-config + - tailscale + - magicdns + - known-hosts + - host-key + - troubleshooting +status: published +created: 2026-06-11 +updated: 2026-06-11 +--- + +# 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: + +```sshconfig +Host MyMac mymac + HostName 100.74.124.81 + User youruser + IdentityFile ~/.ssh/id_ed25519 +``` + +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@ 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 + +1. `ssh -o BatchMode=yes true` — if it fails with `Host key verification + failed` (not `Permission denied`), it's a host-key problem, not auth. +2. `ssh -G | grep -E '^(hostname|user|port) '` — if `hostname` is just your + typed arg and there's no real `HostName`, there's no `Host` block. +3. `ssh-keygen -F ` vs `ssh-keygen -F ` — find which name actually holds + the trusted key. Pin whichever one `known_hosts` has (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 ` Works (Host Alias Bypass)*. diff --git a/SUMMARY.md b/SUMMARY.md index ed771d5..5135b69 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -132,5 +132,6 @@ updated: 2026-05-15T09:00 * [wget/curl: URLs with Special Characters Fail in Bash](05-troubleshooting/wget-url-special-characters.md) * [Ansible: Check Mode False Positives in Verify/Assert Tasks](05-troubleshooting/ansible-check-mode-false-positives.md) * [Ansible Fails with Permission Denied While `ssh ` Works (Host Alias Bypass)](05-troubleshooting/ansible-ssh-host-alias-bypass.md) + * [SSH Alias Falls Through to MagicDNS — Host-Key Verification Failure (No `Host` Block)](05-troubleshooting/networking/ssh-missing-host-block-magicdns-host-key-failure.md) * [Ghost EmailAnalytics Lag Warning — What It Means and When to Worry](05-troubleshooting/ghost-emailanalytics-lag-warning.md) * [claude-mem: --setting-sources Empty Arg Bug (Claude Code 2.1.x)](05-troubleshooting/claude-mem-setting-sources-empty-arg.md)