Documents the non-obvious failure mode where /etc/hosts generator scripts using `tailscale status --json | jq '.HostName'` get poisoned by iOS peers, which always report HostName as the literal string "localhost" because iOS doesn't expose the device name to apps. Includes the buggy and fixed jq filter (use .DNSName first label instead), a real-world Postfix outage example, and a verification checklist. Linked from troubleshooting index and SUMMARY. Discovered while diagnosing a 24h Postfix outage on majordiscord. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.7 KiB
| title | domain | category | tags | status | created | updated | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| iOS Tailscale Clients Report HostName="localhost" — Breaks /etc/hosts Generators | troubleshooting | networking |
|
published | 2026-04-29 | 2026-04-29 |
iOS Tailscale Clients Report HostName="localhost" — Breaks /etc/hosts Generators
Problem
A homegrown script that builds an /etc/hosts block from tailscale status --json silently corrupted the file the moment any iOS device joined the tailnet. After the next run, services bound to localhost started failing.
On the affected host (majordiscord), Postfix refused to start with:
postfix: fatal: parameter inet_interfaces: no local interface found for 100.127.114.10
/etc/hosts looked fine at the top — 127.0.0.1 localhost was still present — but inside the Tailscale-managed block:
# TAILSCALE_START
100.84.42.102 tttpod
100.110.197.17 majortoot
100.95.55.40 localhost <-- WRONG (this is an iPhone)
100.84.165.52 majormail
...
100.127.114.10 localhost <-- WRONG (this is an iPad)
# TAILSCALE_END
When Postfix resolved localhost (because inet_interfaces = localhost in main.cf), the last matching entry in /etc/hosts won — a Tailscale IP that doesn't exist on this host — and the daemon died on bind.
Root Cause
The script used .HostName from the Tailscale JSON:
tailscale status --json \
| jq -r '.Peer[] | "\(.TailscaleIPs[0]) \(.HostName)"' \
>> "$TEMP_HOSTS"
iOS Tailscale clients (iPhone, iPad) always report HostName: "localhost" in the JSON. iOS doesn't expose the real device name to apps the way macOS/Linux/Windows do, so the Tailscale client falls back to the literal string localhost.
Inspect it directly:
$ tailscale status --json | jq '.Peer[] | select(.OS == "iOS") | {DNSName, HostName, OS}'
{
"DNSName": "iphone171.tail7f2d9.ts.net.",
"HostName": "localhost",
"OS": "iOS"
}
{
"DNSName": "ipad166.tail7f2d9.ts.net.",
"HostName": "localhost",
"OS": "iOS"
}
Every iOS device contributes a line <tailscale-ip> localhost to /etc/hosts, hijacking the localhost lookup.
Fix
Use .DNSName (the unique tailnet DNS name) and take the first dotted component instead of .HostName:
tailscale status --json \
| jq -r '.Peer[] | "\(.TailscaleIPs[0]) \(.DNSName | rtrimstr(".") | split(".")[0])"' \
>> "$TEMP_HOSTS"
DNSName is always set, always unique, and produces clean labels like iphone171, ipad166, majorlab, etc.
After patching the script and re-running it:
$ bash /root/update_tailscale_hosts.sh
$ systemctl restart postfix
$ systemctl is-active postfix
active
Why It's Hard to Spot
- The corruption only triggers when an iOS device is in the tailnet — so the script "worked" for months.
/etc/hostsfiles are commonly skimmed top-down. The boguslocalhostline is buried in the Tailscale block, well below the legitimate127.0.0.1 localhostline, and looks superficially like a normal Tailscale entry.- Postfix's error message names the IP, not
localhost, so the connection to/etc/hostsisn't obvious. getent hosts localhostshows the first match (127.0.0.1), not the one Postfix's resolver actually picks forinet_interfaceslookup.
Verification Checklist
If you suspect this on any host using a similar generator script:
# Any non-loopback "localhost" entries are bugs
grep -nE '^[0-9]+\..* localhost\s*$' /etc/hosts
# Look at iOS peers' HostName field
tailscale status --json | jq '.Peer[] | select(.OS == "iOS") | .HostName'
Related
- majordiscord — affected host (incident logged 2026-04-29)
- Network Overview — Tailscale fleet topology