Skip to content

Instantly share code, notes, and snippets.

@adamamyl
Last active March 22, 2026 23:09
Show Gist options
  • Select an option

  • Save adamamyl/81b78eced40feae50eae7c4f3bec1f5a to your computer and use it in GitHub Desktop.

Select an option

Save adamamyl/81b78eced40feae50eae7c4f3bec1f5a to your computer and use it in GitHub Desktop.
Bug Report: macOS 26 breaks /etc/resolver/ supplemental DNS for custom TLDs

Ah, the joys of waking up to find the Mac's done an overnight upgrade… and erm, suddenly things stop working. Thankfully, me and Claude managed to work out what the fuck is going on… I'm sharing here, as well as having raised in on https://feedbackassistant.apple.com/feedback/22280434 (that seems to need a login?).

Bug Report: macOS 26 breaks /etc/resolver/ supplemental DNS for custom TLDs

Product: macOS 26.3.1 (Darwin 25.3.0, Build 25D771280a) Component: Networking → DNS / mDNSResponder Regression from: macOS 25.x 26.3.0 (working immediately prior to overnight update)


Summary

The /etc/resolver/ per-domain DNS resolver mechanism — an Apple-documented, long-standing macOS feature — is silently broken in macOS 26 for any TLD that is not present in the IANA root zone. mDNSResponder intercepts queries for custom/private TLDs and handles them as mDNS (multicast DNS), never consulting the unicast nameserver specified in the resolver file. This breaks an entire class of local development and private network DNS workflows that previously worked correctly on macOS 25 and earlier.


Background

macOS supports per-domain DNS resolver configuration via files placed in /etc/resolver/. A file named /etc/resolver/internal containing nameserver 127.0.0.1 instructs the DNS stack to send all *.internal queries to the local nameserver at 127.0.0.1. This mechanism is documented in man 5 resolver and has worked reliably since at least macOS 10.6. It is widely used by developers running local DNS servers (dnsmasq, bind, unbound) to resolve private domain suffixes.

This machine runs dnsmasq (via Homebrew) as a local DNS resolver, configured to answer queries for *.internal domains (static entries for a local web application) and forward everything else upstream. The /etc/resolver/internal file routes these queries to dnsmasq. This setup worked correctly on macOS 25.x.


Steps to Reproduce

  1. Install dnsmasq and configure it to answer a custom domain:

    # /opt/homebrew/etc/dnsmasq.d/test.conf
    address=/probe.example-private/127.0.0.1
    
  2. Start dnsmasq: brew services start dnsmasq

  3. Verify dnsmasq answers directly:

    dig @127.0.0.1 probe.example-private A +short
    # Returns: 127.0.0.1  ✓
    
  4. Create a resolver file:

    sudo sh -c 'echo "nameserver 127.0.0.1" > /etc/resolver/example-private'
    
  5. Flush DNS cache and restart mDNSResponder:

    sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder
    
  6. Verify scutil --dns shows the resolver is registered:

    scutil --dns | grep -A4 "example-private"
    # Shows: domain: example-private, nameserver: 127.0.0.1  ✓
    
  7. Attempt to resolve via the system resolver:

    ping -c1 probe.example-private
    # ping: cannot resolve probe.example-private: Unknown host  ✗
    
    python3 -c "import socket; print(socket.getaddrinfo('probe.example-private', 80))"
    # socket.gaierror: [Errno 8] nodename nor servname provided, or not known  ✗
    

Expected Behaviour

ping, curl, and any application using getaddrinfo() should resolve probe.example-private to 127.0.0.1, as specified by the dnsmasq address= directive, reached via the /etc/resolver/example-private unicast nameserver entry. This is exactly what happened on macOS 25.x.


Actual Behaviour

All resolution via getaddrinfo() (i.e. every real application — browsers, curl, ping) fails with "Unknown host". No DNS traffic reaches dnsmasq. Instead, mDNSResponder intercepts the query and immediately returns a cached "No Such Record" mDNS response with an anomalously large TTL (~108002 seconds).

Evidence from dns-sd -G v4 probe.example-private:

Timestamp     A/R  Flags         IF  Hostname                      Address  TTL
11:42:03.617  Add  40000002       0  probe.example-private.        0.0.0.0  108002   No Such Record

Evidence from tcpdump -i lo0 -n port 53 captured during a getaddrinfo() call:

0 packets captured

No packets reach dnsmasq on 127.0.0.1:53 at all. mDNSResponder handles the query entirely internally via mDNS and never consults the unicast nameserver.


Scope

Tested TLDs that fail:

TLD Status Notes
.internal Broken IETF draft special-use TLD; worked on macOS 25
.test Broken RFC 6761 §6.2 — explicitly reserved for local testing
.home.arpa Broken RFC 8375 — IANA reserved for residential private networks
.lan Broken Widely used convention (not IANA reserved, but irrelevant)
Arbitrary (e.g. .emflocal) Broken Any TLD not in the IANA root zone

.test is particularly egregious: RFC 6761 Section 6.2 explicitly reserves .test for exactly this use case — local/private DNS testing — and specifies that resolvers SHOULD resolve it via normal DNS mechanisms. macOS 26 silently overrides this by treating it as mDNS-only.

google.com, bbc.co.uk and other standard IANA-registered TLDs continue to work correctly via the default unicast resolver. Only custom/unregistered/special-use TLDs are affected.


Workaround

The only reliable workaround is to add entries manually to /etc/hosts, which bypasses mDNSResponder entirely. This is impractical for dynamic use cases (e.g. Docker container DNS, where host entries change frequently) and requires sudo for every change.


Impact

This breaks the standard local development DNS workflow that has been documented and recommended by the macOS developer community for over a decade:

  • Any developer using dnsmasq + /etc/resolver/ for *.test, *.local, *.internal, or other private TLDs
  • Docker Desktop's (and similar tools') container name resolution via custom TLDs
  • Any tool that generates /etc/resolver/ entries as part of its macOS integration (e.g. Vagrant, Tailscale, various VPN clients)
  • Kubernetes local development tools (minikube, kind, k3d) that use *.cluster.local or similar

The failure is silent: scutil --dns correctly shows the resolver configuration is registered, leading users to believe the setup is correct while resolution silently fails. There is no log output, no error, and no indication that mDNS interception is occurring.


Environment

  • macOS version: 26.3.1 (ProductVersionExtra: (a))
  • Build: 25D771280a
  • Hardware: Apple Silicon (arm64)
  • Regression: Working on macOS 25.x immediately before overnight system update
  • dnsmasq version: Homebrew, listening on 127.0.0.1:53
  • Verified via: dig @127.0.0.1 (works), host (works — uses own resolver), ping/curl/python3 socket.getaddrinfo (all fail)

References

  • man 5 resolver (macOS) — documents the /etc/resolver/ mechanism
  • RFC 6761 — Special-Use Domain Names (reserves .test, .localhost, .invalid, .example)
  • RFC 8375 — Special-Use Domain home.arpa
  • IETF draft-ietf-dnsop-interneti-mdn — .internal special-use proposal
@peterc
Copy link

peterc commented Mar 19, 2026

Ah, I wonder if that explains why I had to turn the fancy DNS features of Tailscale off and on again a few times to get them to work again (which they did!)

@Frizlab
Copy link

Frizlab commented Mar 19, 2026

You’re supposed to use scutil now since a long time ago actually.

@adamamyl
Copy link
Author

Ah, I wonder if that explains why I had to turn the fancy DNS features of Tailscale off and on again a few times to get them to work again (which they did!)

Tailscale's been OK for me…

@adamamyl
Copy link
Author

You’re supposed to use scutil now since a long time ago actually.

For diagnostics, I'm aware (as is my pal, Claude). It doesn't however, allow running an equivalent of dnsmasq, does it? (If it does, that could be interesting)

@adamamyl
Copy link
Author

Thanks for tracking this down and sharing it. There are a number of references to "macOS 25" like "Regression from: macOS 25.x" and "previously worked correctly on macOS 25 and earlier" - shouldn't that be "macOS 15" instead?

uname includes the version, but I guess for most people they think of it by name (although tbf, I can never remember the order of any of them…). My Sequoia (Intel) Mac gives Darwin Kernel Version 24.6.0.

@ralgozino
Copy link

ralgozino commented Mar 19, 2026

I am using this with Tahoe 26.3.1; the only difference with my setup is that my local dns server (coredns instead of dnsmasq but this should be irrelevant) is listening on the 5300 port instead of 53.

I had to configure it to listen on that port because otherwise it will conflict with macos internet sharing dns IIRC.

I'd suggest you try configuring dnsmasq to listen on port 5300 (or any other free port) and add a port 5300 line to the file in /etc/resolver/<domain>.

@tinyapps
Copy link

Thanks for tracking this down and sharing it. There are a number of references to "macOS 25" like "Regression from: macOS 25.x" and "previously worked correctly on macOS 25 and earlier" - shouldn't that be "macOS 15" instead?

uname includes the version, but I guess for most people they think of it by name (although tbf, I can never remember the order of any of them…). My Sequoia (Intel) Mac gives Darwin Kernel Version 24.6.0.

Yes, but that still doesn't work, since Tahoe/macOS 26.3.1 is running Darwin 25.3.0 as reported by uname -r.

@vsviridov
Copy link

Can you set the file type to markdown so it's rendered?

@adamamyl
Copy link
Author

adamamyl commented Mar 19, 2026

Can you set the file type to markdown so it's rendered?

Done. Thanks for mentioning — I thought it had auto-detected it was Markdown.

@nightpool
Copy link

This setup worked correctly on macOS 25.x.

😭

@per-allansson
Copy link

Try using a non-loopback IP and see if it works then.

@BeauSlim
Copy link

My setup is a little different, so this is just a data point for you. I use a ".home" TLD via dnsmasq on a couple OpenWRT APs for local resolution. I have a "home" file and a "10.in-addr.arpa" file in /etc/resolver on my three macs.
pings from the command line or accessing http://whatever.home in Firefox, so /etc/resolver is working fine for me even after yesterday's security patch.

@zlogic
Copy link

zlogic commented Mar 19, 2026

I've seen similar issues as well, at least with Safari.

  1. The "Limit IP address tracking" option in the network adapter settings seems to mess with macOS' DNS resolver. Even without a paid or enabled iCloud Private Relay. It seems like "Limit IP address tracking" might be forcing macOS to use a fixed DoH or DoT DNS server (this article mentions CloudFlare/Akamai: https://basila.medium.com/ios-15-2-now-uses-cloudflares-akamai-s-dns-by-default-for-your-device-s-traffic-95d0f805a827)
  2. It's not ideal but completely overriding the DNS on the network interface helped.

@cmatthias
Copy link

Thanks for tracking this down and sharing it. There are a number of references to "macOS 25" like "Regression from: macOS 25.x" and "previously worked correctly on macOS 25 and earlier" - shouldn't that be "macOS 15" instead?

uname includes the version, but I guess for most people they think of it by name (although tbf, I can never remember the order of any of them…). My Sequoia (Intel) Mac gives Darwin Kernel Version 24.6.0.

The darwin kernel version has nothing to do with the macOS version. The version of macOS immediately prior to 26 was 15.

@pmccarren
Copy link

Interesting - I run a nearly identical set, with many TLDs (including .internal) configured in /etc/resolver/X and dnsmasq handling the resolve and I have not had an issue on macos26.

the resolver confs all contain this content:

# /etc/resolver/example-private
nameserver: 127.0.0.1
domain example-private

I noticed in the author's bug report they do not include domain, which is documented in man 5 resolver as:

The domain directive is only necessary, if your local
router advertises something like localdomain and you have
set up your hostnames via an external domain.

In the real world though, I've found the domain setting to be required nearly every time.

I wonder if adding it will resolve the issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment