- fail2ban-digest-mode-fleet: recidive-only email model, sshd now silent, defaults-debian.conf gotcha added - netdata-docker-health-alarm-tuning: 30m/10m config, tuning history table - New: wp-fail2ban-logpath-debian-ubuntu, lora-adapter-gguf-conversion-fails, tailscale-status-json-hostname-localhost-ios - Various article updates and nav index refreshes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
116 lines
3.7 KiB
Markdown
116 lines
3.7 KiB
Markdown
---
|
|
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
|