---
title: Fixing Inaccessible Tailscale Services on my Phone
description: Would you believe it was DNS?
publishedDate: 2025-04-05
updatedDate:
  - 2025-04-06
imageURL: /blog/fixing-tailscale-dns/carbon.png
tags:
  - networking
  - sysadmin
---
Some of the services I host are only exposed within my [Tailnet](https://tailscale.com/kb/1136/tailnet), and for the longest time I've been able to access them from everywhere *but* my Android phone. It completely stumped me - I thought there was something broken in the Tailscale config on my phone, but as it turns out, it wasn't a them problem - it was a me problem.

TL;DR: don't use CNAME records with Tailscale's MagicDNS domains (like `wall-e.xerus-mizar.ts.net`).

```diff
- CNAME("cavill.internal.infra", "cavill.xerus-mizar.ts.net."),
- CNAME("wall-e.internal.infra", "wall-e.xerus-mizar.ts.net."),

+ A("cavill.internal.infra", "100.102.93.102"),
+ AAAA("cavill.internal.infra", "fd7a:115c:a1e0::2326:5d66"),
+ A("wall-e.internal.infra", "100.82.80.41"),
+ AAAA("wall-e.internal.infra", "fd7a:115c:a1e0::9d01:5029"),
```

# The Setup

Since lots of my services are spread over a few domains and I change the machines that I use to host my stuff semi-regularly, all my domains use CNAME records to point to a single source of truth per machine. For example - the domain this website is on looks like this:

```
$ dig www.akpain.net
www.akpain.net.          30     IN      CNAME   cavill.infra.tedted.dev.
cavill.infra.tedted.dev. 30     IN      A       65.108.144.151
```

I have records for the clearnet IPs of my machines and records for the Tailnet IPs of my machines - both `wall-e.infra.tedted.dev` and `wall-e.internal.infra.tedted.dev` point to the same machine, but the latter is via Tailscale only.

While the clearnet versions use straight IP addresses, the internal versions are implemented using a CNAME to the Tailscale [MagicDNS](https://tailscale.com/kb/1081/magicdns) domains. Tailscale gives each machine in a Tailnet an IP address that's only valid when connected to your Tailnet. When MagicDNS is enabled, it also creates some domain name records like `wall-e.xerus-mizar.ts.net` that make those IPs easier to use - setting up CNAMEs to point to these records worked perfectly fine on my Linux laptop when I was setting them up, so I assumed the job was a good-'un and moved on with a config that looked like this:

```
$ dig cloud.tedted.dev
cloud.tedted.dev.                 30      IN      CNAME   wall-e.internal.infra.tedted.dev.
wall-e.internal.infra.tedted.dev. 30      IN      CNAME   wall-e.xerus-mizar.ts.net.
wall-e.xerus-mizar.ts.net.          30      IN      A       100.82.80.41
```

This has never worked on my phone though - I'd always get NXDOMAIN errors and I'd never be able to connect, even when I had Tailscale switched on and I could ping the machines via the Tailscale app or access them directly with IP addresses.

# How DNS Resolution Works

To explain why this didn't work needs a quick explanation of DNS.

When a DNS server receives a query for a domain name, it performs recursive queries to other DNS servers to retrieve IPs associated with the domain names it was asked for. When it encounters a CNAME record in this process, it will go away and implicitly resolve that CNAME too, meaning an IP address is always returned to the client in the end. The cruicial point is that the CNAME is resolved on that remote DNS server.

Tailscale's MagicDNS works by inserting its own DNS servers into a machine's local networking stack to intercept and overwrite requests that would otherwise not resolve to anything public. This means it can intercept MagicDNS requests and rewrite the response to include the IP address of the machine on the Tailnet.

# Joining the Dots

Tailscale being able to insert its own DNS servers onto my Linux system meant that all MagicDNS names could be resolved easy-peasy. My laptop (and all Linux systems, I think) run a local DNS resolver for use by that machine only, meaning that the resolver I was using on my laptop was also inside of my Tailnet.

However, on Android, it's not so cut-and-dry: often, Android is configured to use DNS-over-HTTPS at a system level, and apps can also sometimes use their own custom DNS servers that don't rely on the system ones. In both cases, suddenly, the DNS resolver in use is not in control of the local device and is hence falls completely outside of the Tailnet.

The real-world effect of this is that MagicDNS names simply cannot be resolved when they're the target of a CNAME record. It can be made to resolve `*.ts.net` names when queried directly by setting up some filtering rules to route `*.ts.net` queries to the local device, but this isn't going to do anything for, say, `akpain.net` names that eventually degrade to a `*.ts.net` name on a remote resolver that Tailscale can't see.

So, the fix to my services hosted on Tailscale being inaccessible?

Include the raw IP addresses in my DNS config instead of lazily using CNAMEs to point to pre-existing records that actually, as it turns out, don't always exist.

It's always DNS.

```{figure} the-commit.png
:alt: A screenshot of the Gitlab commit information page showing a commit altering DNS records to use IP addresses instead of CNAMEs to Tailscale domains
```
