Connecting iPhone and Apple TV to Headscale
In the previous post on self-hosting Headscale, I covered deploying the coordination server, fixing deprecated config keys, and connecting macOS and Linux clients with pre-auth keys. That part is straightforward: generate a key, run tailscale up --authkey=<key>, done.
iOS and tvOS don’t work that way.
Why pre-auth keys don’t work on iOS
Pre-auth keys are a Tailscale feature. The official iOS client supports them for the hosted Tailscale service, but when you point the client at a custom coordination server, pre-auth key authentication isn’t supported. This is a known limitation in the upstream iOS app, not a Headscale bug.
The workaround is interactive registration: the iOS client generates a registration key, you find it in the server logs, and approve the device via the Headscale CLI. It works, but it’s a few more steps than the one-liner on other platforms.
iOS setup
Open the Tailscale app on your iPhone. Go to Settings (the gear icon at the top right), tap your account name, then Log Out.
From the login screen, tap Log In. You’ll see a field to enter a custom coordination server. Enter your Headscale URL:
https://headscale.example.net
The app loads a page and waits. At this point, the device is trying to register but needs approval. On your Headscale server, check the logs to find the registration key it generated:
sudo docker logs headscale 2>&1 | tail -20
Look for a line like:
INF Starting node registration using key: nodekey:abc123...
Copy that key and register the device:
sudo docker exec headscale headscale nodes register --user homelab --key nodekey:abc123...
The --user flag here takes the username string directly, not a numeric ID. That’s different from preauthkeys create, which uses -u <numeric-ID>.
The iOS app shows “Connected” as soon as the registration goes through.
The localhost hostname problem
After registration, check what name your iPhone registered with:
sudo docker exec headscale headscale nodes list
You’ll see something like:
ID | Hostname | Name | Last Seen | Connected
1 | localhost | localhost | 2025-06-17 10:22:10 | online
The iPhone registers with hostname localhost. That’s technically accurate from the phone’s perspective, but it makes the node list confusing.
Rename it:
sudo docker exec headscale headscale nodes rename --identifier 1 iphone
The Name column is what Headscale and other Tailscale clients see. localhost stays in Hostname (it’s what the device reported), but iphone is what shows up in the node list going forward.
tvOS (Apple TV) setup
Apple TV follows the same interactive registration flow, but the input method is different - you’re working with a TV remote, not a keyboard.
On your Apple TV, open the Tailscale app. Go to Settings → Account → Log In. The app asks for a custom coordination server and then displays a QR code on the screen.
Scan the QR code with your iPhone. It opens a browser to a Headscale registration URL. The Apple TV is now waiting for approval.
Back on the server, check the logs for the registration key:
sudo docker logs headscale 2>&1 | tail -20
Register the device the same way:
sudo docker exec headscale headscale nodes register --user homelab --key nodekey:xyz789...
Give it a useful name:
sudo docker exec headscale headscale nodes rename --identifier 2 appletv
The TV shows “Connected.”
VPN-on-demand and home WiFi
The Tailscale iOS app has a VPN On Demand feature that automatically connects when you leave home. In practice this means: the VPN connects when you’re on cellular or an unknown network, and doesn’t connect when you’re on your home WiFi.
If you look at Headscale’s node list while your iPhone is on home WiFi, it shows the device as offline. That’s expected. The VPN isn’t running - the phone doesn’t need Headscale to reach home resources when it’s already on the home network.
When you leave and connect via cellular, Tailscale connects automatically and the node shows online again.
Comparing platforms
| Platform | Pre-auth keys | Method |
|---|---|---|
| macOS | Yes | tailscale up --authkey=<key> |
| Linux | Yes | tailscale up --authkey=<key> |
| iOS | No | Interactive login → CLI nodes register |
| tvOS | No | QR code → CLI nodes register |
The pre-auth key path is fast: generate a key on the server, run one command on the client, done. The interactive path adds a few steps but isn’t complicated once you know what the logs look like.
Lessons
Check the logs immediately. After the iOS app shows its registration screen, run docker logs headscale | tail -20 right away. The node key appears once and doesn’t persist anywhere obvious. If you miss it, log out of the app and log back in to generate a new one.
Rename before you forget. Once localhost is in your node list, it’s easy to lose track of which device it is. Rename immediately after registration.
--user takes a name, not a number. headscale nodes register --user homelab works. Coming from preauthkeys create -u 1 (numeric), I expected the same flag here - it’s not.
The tvOS QR code is the friendliest part. Entering a URL with a TV remote would be painful. The QR code flow is well-designed: scan with phone, approval comes from the server CLI, TV shows connected.
Related reading
Self-hosting Tailscale with Headscale
Replacing Tailscale's hosted coordination server with Headscale on a Proxmox LXC: Docker Compose setup, deprecated config keys that blocked startup, and connecting macOS, Linux, and iOS clients.
UniFi APs offline after restart: how the wrong config file path breaks everything
After a container restart, both APs went offline even though the controller was healthy. The cause: system_ip in /config/system.properties instead of /config/data/system.properties. UniFi silently ignores the root file and falls back to the Docker bridge IP.
Researching MikroTik RouterOS DNS proxy for the homelab
What RouterOS DNS proxy does, how it would touch DHCP, firewall rules, and static DNS, and why I left clients talking to Pi-hole directly.
Ready to Transform Your Career?
Let's work together to unlock your potential and achieve your professional goals.