1 Why modern Linux ships 127.0.0.53
Distributions adopted systemd-resolved so user space no longer needed to rewrite /etc/resolv.conf every time DHCP, VPN, or container networking tweaked upstream resolvers. Instead, the resolver daemon owns a cache, honors per-interface DNS from NetworkManager or systemd-networkd, and exposes a single stable stub address—almost always 127.0.0.53—that /etc/resolv.conf references via a symlink into /run/systemd/resolve/. From an application’s perspective, nothing exotic happens: it calls getaddrinfo, libc forwards the query to the listed nameserver, and systemd-resolved decides how to forward or cache the answer.
Transparent proxies such as Mihomo introduce a second policy layer. TUN captures selected IP packets and can rewrite destinations, but DNS is not automatically unified unless the OS stack consistently forwards queries into Mihomo’s internal resolver or unless you hijack DNS at the right boundary. If resolved still believes it should talk to your ISP or corporate resolvers while Mihomo rewrites outbound paths, you can observe correct browser behavior (because Chromium maintains its own cache or uses DoH) while CLI tools fail, or you can watch intermittent leaks when one resolver path bypasses your rule engine altogether.
The confusion intensifies with FakeIP and splithorizon rules: Mihomo may synthesize answers in 198.18.0.0/15 while resolved still performs independent recursion that never sees those records. Aligning systemd-resolved with Mihomo therefore becomes a hygiene step—not an optional tweak—for anyone who expects a single coherent DNS authority on the host.
2 Typical failure modes with Mihomo TUN
Operators usually notice the mismatch long after enabling TUN, because the graphical shell “works” while automation breaks. Common patterns include:
- Persistent 127.0.0.53 lookups: Packet captures or Mihomo logs show DNS leaving the box toward the stub even though TUN is active, meaning resolved still mediates resolution for legacy stacks.
- Loops and catastrophic latency: resolved forwards to an address that routes back through Mihomo, which forwards DNS again to resolved—each hop adds exponential delay until queries time out.
- Split leaks: Some processes obtain FakeIP answers while others obtain real addresses, causing TLS SNI, routing rules, and GeoIP databases inside Mihomo to disagree about the same hostname.
- Containers and systemd-nspawn: Each namespace may copy resolv.conf independently; unless they inherit the corrected stub policy, they can exhibit different DNS than the host TUN stack.
None of these symptoms disprove TUN itself—they signal that the DNS edge is still split. The rest of this guide shows how to collapse that edge deliberately rather than debugging by randomly toggling GUI options.
3 Inventory: know your resolver chain before editing files
Start every troubleshooting session by mapping three artifacts: the effective resolv.conf symlink target, the runtime state reported by resolvectl, and Mihomo’s DNS listener configuration in your YAML or GUI-derived profile. Run the following with Mihomo both stopped and running (if safe) to see how state diverges.
# Where does libc send DNS?
readlink -f /etc/resolv.conf
# Current upstreams, domains, and per-link DNS
resolvectl status
# Who listens on UDP/53 and TCP/53?
ss -lunp | grep -E ':53\\b' || true
ss -ltnp | grep -E ':53\\b' || true
If /etc/resolv.conf points at stub-resolv.conf, you are in the default stub mode. Note which global or per-interface DNS servers resolved lists—they are the ones Mihomo must replace or subsume. Cross-check Mihomo’s configured dns.listen (often 0.0.0.0:1053 or 127.0.0.1:1053) and confirm the port is not colliding with resolved’s stub or with dnsmasq from a hypervisor bridge.
resolvectl status and a copy of your Mihomo profile. Rollback is faster when you can diff rather than guess which override broke DHCP integration.
4 Preferred fix: forward systemd-resolved to Mihomo’s DNS listener
The least disruptive integration pattern keeps systemd-resolved online for DHCP and VPN hooks but instructs it to treat Mihomo as the only upstream for global resolution. You accomplish this with a drop-in under /etc/systemd/resolved.conf.d/. The exact key names vary slightly by systemd vintage, so compare with man resolved.conf on your machine; a representative layout looks like this:
sudo mkdir -p /etc/systemd/resolved.conf.d
sudo tee /etc/systemd/resolved.conf.d/mihomo-dns.conf ><'EOF'
[Resolve]
# Send stub queries to Mihomo; adjust port to match dns.listen
DNS=127.0.0.1
FallbackDNS=
DNSSEC=no
EOF
sudo systemctl restart systemd-resolved
If Mihomo listens on a non-default port such as 1053, recent systemd releases accept annotated forms like 127.0.0.1:1053 or 127.0.0.1#1053 in the DNS= line—verify against your distribution’s documentation. When systemd cannot express non-53 listeners, bind Mihomo’s DNS to port 53 on loopback instead (only after confirming resolved’s stub is not already occupying that address) or keep resolved as the stub while using Mihomo’s hijack-dns TUN feature to redirect stub traffic transparently.
After restarting systemd-resolved, revisit resolvectl status. Global DNS should now show your loopback Mihomo entry, and queries from ordinary applications should traverse Mihomo’s fake-ip engine or redir-host stack exactly once. If you rely on split DNS for corporate domains, add appropriate Domains=~corp.example entries or manage those suffixes inside Mihomo rule providers instead of letting resolved fight your policy.
5 Advanced pattern: disable the stub listener with care
Some operators prefer to stop multiplexing entirely. Setting DNSStubListener=no in /etc/systemd/resolved.conf removes the 127.0.0.53 stub so you can point /etc/resolv.conf directly at Mihomo or at a local dnsmasq front-end. This path is powerful but brittle: anything that expects resolved’s automatic merging of Wi-Fi, wired, and VPN DNS may silently break, and unattended upgrades occasionally revert your symlink chain.
If you choose this route, rebuild /etc/resolv.conf as a regular file listing nameserver 127.0.0.1 (matching Mihomo’s listener) and document the change in your configuration management system. Pair the change with monitoring—when Mihomo crashes, the entire host loses resolution, whereas the forwarding model often lets resolved fall back to a corporate resolver if you leave a controlled FallbackDNS= entry. Never run this configuration on remote servers without out-of-band console access.
6 Mihomo YAML knobs that must agree with resolved
DNS integration is bilateral. After nudging systemd-resolved, open your Mihomo profile and confirm the dns: section mirrors what you advertise to the OS. At minimum, set enable: true, define a non-conflicting listen address, and keep enhanced-mode consistent with your leak model—FakeIP accelerates domain-based rules but insists that every consumer resolves through Mihomo. Our deep dive on Meta core DNS leak prevention explains how DoH upstreams, fallback ladders, and FakeIP caches interact; the same principles apply when resolved is the outer shell.
Under tun:, verify enable is true, decide whether you need auto-route and strict-route, and pay attention to stack selection (system vs gvisor) on constrained kernels. If you enable hijack-dns or the equivalent “DNS hijacking” toggle in a GUI wrapper, ensure it targets the same interfaces resolved uses; duplicate hijacks can duplicate packets. When experimenting, raise log-level temporarily to debug and watch whether DNS queries originate from 127.0.0.53 or directly from application PIDs—those hints tell you whether the OS integration succeeded.
GUI clients versus raw YAML
Desktop wrappers may regenerate YAML fragments on each save, silently undoing your resolved drop-in assumptions. If you rely on Clash Verge Rev or a similar client, sync the GUI’s DNS listen port with resolved.conf.d, disable conflicting “boost” DNS modules, and export the effective profile occasionally to diff against your server template. The goal is one authoritative resolver graph, not two wizards fighting for the same loopback port.
7 Verify, benchmark, and roll back cleanly
Validation should mix observability and functional tests. Use resolvectl query example.com to ensure answers reflect your Mihomo mode—FakeIP deployments often return addresses inside the synthetic range, while redir-host mode should show real upstream mapping. From another terminal, run dig @127.0.0.53 example.com before and after changes; latency should drop once loops disappear.
Application-level checks matter: trigger curl https://example.com, launch a container with podman run --rm alpine ping -c1 example.com, and exercise whichever language runtime you depend on (Node, Go, Rust) because each may cache libc results differently. Capture a short tcpdump -i lo port 53 trace if you still suspect shadow resolvers—unexpected secondary stubs mean another package inserted local forwarding.
If results regress, restore your archived drop-ins, restart resolved, and reload Mihomo. Most misconfigurations are reversible in under a minute when backups exist, which is why the inventory step emphasized saving resolvectl status output before you touch files.
8 Wrap-up
systemd-resolved’s stub at 127.0.0.53 is not an enemy of Mihomo—it is an integration surface. Treat it deliberately: either forward stub queries into Mihomo’s DNS listener or retire the stub with a sober rollback plan. Pair those OS changes with consistent Mihomo dns and tun settings so FakeIP, routing rules, and upstream recursion share one mental model.
Compared with ad hoc per-app proxy toggles, collapsing DNS authority reduces stray leaks and makes logs interpretable again. Desktop clients that bundle Mihomo can deliver the same outcome with less terminal work, provided you keep GUI automation from undoing the resolved drop-ins you rely on.
When you want installers that track Mihomo releases across Windows, macOS, and Linux—without rebuilding YAML by hand—the maintained builds on our download hub usually provide a smoother path than stitching binaries yourself, especially once TUN and DNS overrides need to coexist with desktop security tooling.