wiki: add troubleshooting article — iOS Tailscale clients report HostName="localhost"
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>
This commit is contained in:
parent
ad2f12c16e
commit
9de839066c
3 changed files with 120 additions and 2 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
created: 2026-03-15T06:37
|
created: 2026-03-15T06:37
|
||||||
updated: 2026-04-29T22:45
|
updated: 2026-04-29T23:55
|
||||||
---
|
---
|
||||||
# 🔧 General Troubleshooting
|
# 🔧 General Troubleshooting
|
||||||
|
|
||||||
|
|
@ -14,6 +14,7 @@ Practical fixes for common Linux, networking, and application problems.
|
||||||
- [Mail Client Stops Receiving: Fail2ban IMAP Self-Ban](networking/fail2ban-imap-self-ban-mail-client.md)
|
- [Mail Client Stops Receiving: Fail2ban IMAP Self-Ban](networking/fail2ban-imap-self-ban-mail-client.md)
|
||||||
- [firewalld: Mail Ports Wiped After Reload](networking/firewalld-mail-ports-reset.md)
|
- [firewalld: Mail Ports Wiped After Reload](networking/firewalld-mail-ports-reset.md)
|
||||||
- [Tailscale SSH: Unexpected Re-Authentication Prompt](networking/tailscale-ssh-reauth-prompt.md)
|
- [Tailscale SSH: Unexpected Re-Authentication Prompt](networking/tailscale-ssh-reauth-prompt.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)
|
- [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)
|
- [Windows OpenSSH: WSL Default Shell Breaks Remote Commands](networking/windows-openssh-wsl-default-shell-breaks-remote-commands.md)
|
||||||
- [Pi-hole AI Blocklist Blocks Claude Desktop (ERR_CONNECTION_REFUSED)](networking/pihole-blocks-claude-desktop.md)
|
- [Pi-hole AI Blocklist Blocks Claude Desktop (ERR_CONNECTION_REFUSED)](networking/pihole-blocks-claude-desktop.md)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
---
|
||||||
|
title: iOS Tailscale Clients Report HostName="localhost" — Breaks /etc/hosts Generators
|
||||||
|
domain: troubleshooting
|
||||||
|
category: networking
|
||||||
|
tags:
|
||||||
|
- tailscale
|
||||||
|
- ios
|
||||||
|
- postfix
|
||||||
|
- etc-hosts
|
||||||
|
- jq
|
||||||
|
status: published
|
||||||
|
created: 2026-04-29
|
||||||
|
updated: 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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ 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`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
$ 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/hosts` files are commonly skimmed top-down. The bogus `localhost` line is buried in the Tailscale block, well below the legitimate `127.0.0.1 localhost` line, and looks superficially like a normal Tailscale entry.
|
||||||
|
- Postfix's error message names the IP, not `localhost`, so the connection to `/etc/hosts` isn't obvious.
|
||||||
|
- `getent hosts localhost` shows the *first* match (`127.0.0.1`), not the one Postfix's resolver actually picks for `inet_interfaces` lookup.
|
||||||
|
|
||||||
|
## Verification Checklist
|
||||||
|
|
||||||
|
If you suspect this on any host using a similar generator script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
created: 2026-04-02T16:03
|
created: 2026-04-02T16:03
|
||||||
updated: 2026-04-29T22:45
|
updated: 2026-04-29T23:55
|
||||||
---
|
---
|
||||||
* [Home](index.md)
|
* [Home](index.md)
|
||||||
* [Linux & Sysadmin](01-linux/index.md)
|
* [Linux & Sysadmin](01-linux/index.md)
|
||||||
|
|
@ -91,6 +91,7 @@ updated: 2026-04-29T22:45
|
||||||
* [Ollama Drops Off Tailscale When Mac Sleeps](05-troubleshooting/ollama-macos-sleep-tailscale-disconnect.md)
|
* [Ollama Drops Off Tailscale When Mac Sleeps](05-troubleshooting/ollama-macos-sleep-tailscale-disconnect.md)
|
||||||
* [Ollama: `ollama run` with Piped Stdin Bypasses Chat Template + SYSTEM Prompt](05-troubleshooting/ollama-chat-template-pipe-stdin-bypass.md)
|
* [Ollama: `ollama run` with Piped Stdin Bypasses Chat Template + SYSTEM Prompt](05-troubleshooting/ollama-chat-template-pipe-stdin-bypass.md)
|
||||||
* [rsync over Tailscale: Hung in TCP Teardown After Transfer Completes](05-troubleshooting/networking/rsync-tailscale-teardown-stall.md)
|
* [rsync over Tailscale: Hung in TCP Teardown After Transfer Completes](05-troubleshooting/networking/rsync-tailscale-teardown-stall.md)
|
||||||
|
* [iOS Tailscale Clients Report HostName="localhost" — Breaks /etc/hosts Generators](05-troubleshooting/networking/tailscale-status-json-hostname-localhost-ios.md)
|
||||||
* [macOS: Repeating Alert Tone from Mirrored iPhone Notification](05-troubleshooting/macos-mirrored-notification-alert-loop.md)
|
* [macOS: Repeating Alert Tone from Mirrored iPhone Notification](05-troubleshooting/macos-mirrored-notification-alert-loop.md)
|
||||||
* [ClamAV CPU Spike: Safe Scheduling with nice/ionice](05-troubleshooting/security/clamscan-cpu-spike-nice-ionice.md)
|
* [ClamAV CPU Spike: Safe Scheduling with nice/ionice](05-troubleshooting/security/clamscan-cpu-spike-nice-ionice.md)
|
||||||
* [Ansible: Vault Password File Not Found](05-troubleshooting/ansible-vault-password-file-missing.md)
|
* [Ansible: Vault Password File Not Found](05-troubleshooting/ansible-vault-password-file-missing.md)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue