What Counts as a DNS Leak?

A DNS leak occurs when domain lookups take an unexpected route. For example, your browser traffic may go through a proxy while domain queries are still handled by your local ISP's DNS servers. A website loading successfully or your public IP changing does not, by itself, prove that DNS queries followed the intended route. Check application connections, DNS upstreams, and the actual egress path of DNS connections separately.

This guide is for clients using the mihomo core. Support for configuration fields varies between legacy Clash cores and clients. Before making changes, check the client's About or core information page and note the core name, version, and configuration source. Menu names vary by client, so the steps below refer directly to configuration fields rather than assuming a particular client's settings layout.

Test DNS for Leaks: Establish a Baseline, Then Compare

1. Record the Results Before Routing Traffic

  1. Pause proxy routing and note your current public IP, the DNS addresses configured on your system, and your browser's Secure DNS settings.
  2. Visit https://www.dnsleaktest.com/, run both the Standard test and Extended test, and record the resolver IPs, organization names, and test time.
  3. Enable the system proxy or TUN mode you use, clear the cache, then run both tests again in the same browser.
  4. Keep the node, browser, and network unchanged. Adjust one DNS setting at a time and repeat the tests. Don't change the node, resolver, and routing mode all at once.

Online tests typically use random subdomains to identify recursive resolvers. The addresses shown usually belong to the resolvers' recursive egress servers—not your device's egress—and may differ from configured access addresses such as 1.1.1.1. Public DNS Anycast routing and geolocation database errors can also affect the displayed location. A mismatch between the resolver's country and the proxy node's country is a clue, not proof of a leak.

2. Clear the Cache and Keep Test Conditions Consistent

On Windows, run the command below in a terminal. The second macOS command requires administrator privileges. After clearing the cache, close and reopen your browser and reconnect to the proxy if needed. This helps prevent existing connections from reusing the old DNS route.

# Windows
ipconfig /flushdns

# macOS
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder

# Linux with systemd-resolved
resolvectl flush-caches

Not all Linux distributions use systemd-resolved. If the command isn't available, check which DNS resolver service your system uses. For each test, record five things: the time, routing mode, browser Secure DNS status, resolver organization, and actual connection egress. There are no made-up test percentages here—use repeated results from your own device to verify that a fix works.

3. Interpret the Evidence—Don't Just Count Addresses

What you seePossible causeWhat to check next
A local ISP resolver appearsSystem DNS isn't being intercepted, or a rule explicitly sends it directCheck TUN, requests on port 53, and DNS routing rules
Only public resolvers appearThe core or browser is using public DNSCheck connection egress; this alone doesn't prove traffic uses the proxy
Multiple public resolvers appearParallel lookups, fallback, or a resolver clusterMatch each result to the configured upstreams
Browser results differ from command-line resultsBrowser-specific DoH, caching, or different application proxy settingsUse a consistent DNS entry point, then test again

fake-ip and nameserver: Set Up a DNS Entry Point You Can Verify

enhanced-mode: fake-ip makes the core return virtual addresses for supported domains, then uses the mapping to handle connections. This reduces the need to resolve real addresses before some connections are established, but it doesn't eliminate all upstream queries. Direct connections, IP-based rule matching, and some applications may still need real DNS results.

This snippet is for checking the DNS path, not a complete subscription configuration. Merge fields with the same names into your existing configuration, keeping your nodes, proxy groups, and rules. Don't define two dns: blocks at the same level. In this example, 7890 is the mixed proxy port and 1053 is the local DNS listening port. They are not interchangeable.

mixed-port: 7890
mode: rule

dns:
  enable: true
  listen: 127.0.0.1:1053
  enhanced-mode: fake-ip
  fake-ip-range: 198.18.0.1/16
  fake-ip-filter:
    - "localhost"
    - "*.lan"
  nameserver:
    - https://1.1.1.1/dns-query
    - https://8.8.8.8/dns-query
  • enable turns on the core's DNS server. Enabling it alone does not make every application send requests to that listening address.
  • listen binds the service to the loopback address for local testing. Don't listen on all network interfaces just to troubleshoot; that could expose a DNS server to your local network.
  • Domains in fake-ip-filter are resolved to real addresses. Add entries only when needed for compatibility; don't exclude every domain just to fix one application.
  • nameserver specifies upstream resolvers. Multiple upstreams do not guarantee a strict primary-and-backup order—don't assume the second address is used only after the first one fails completely.

Use the command below to check whether the core is listening for UDP DNS queries. For domains not excluded by a filter, you should see a virtual address in the 198.18.0.0/16 range. This confirms that the query reached the core, but not that your browser uses the same entry point.

# Windows
nslookup -port=1053 example.com 127.0.0.1

# macOS / Linux with dig installed
dig @127.0.0.1 -p 1053 example.com A

DoH Egress: Encrypted Doesn't Mean Proxied

The DoH endpoint in this example typically uses HTTPS over TCP port 443, so query contents aren't sent as plain UDP traffic on port 53. But if the connection goes direct, the resolver can still see your local network's public IP, and your local network can still see the resolver's destination address. To make the resolver see the proxy node's IP, check how the core routes its DNS connections.

Resolve Node Hostnames Before Using respect-rules

In mihomo versions that support it, dns.respect-rules: true makes DNS connections follow routing rules. It doesn't force all DNS traffic through a proxy: if the resolver IP matches DIRECT, the connection still goes direct. Add a rule that routes the selected resolver's destination through an existing proxy policy, and make sure an earlier direct rule doesn't match first.

When enabling this, also configure proxy-server-nameserver to resolve proxy node hostnames at startup. Otherwise, you can create a dependency loop: connecting to the proxy requires resolving the node's hostname, while resolving it requires connecting to the proxy. The bootstrap resolver must also suit your network. If it resolves node hostnames directly, those names will be visible to that resolver—decide whether that tradeoff is acceptable.

When using a domain-based DoH URL, you also need to resolve the resolver's hostname at startup. default-nameserver is for this bootstrap lookup, not the default routing policy for regular queries. The example above uses an IP-based DoH URL to narrow down the troubleshooting scope, but you still need to verify upstream reachability, TLS validation, and actual egress.

fallback and nameserver-policy: Avoid Unintended DNS Paths

fallback May Trigger Parallel Queries

Don't assume fallback runs only after the primary DNS server times out. Once configured, the core may query the primary and fallback resolvers in parallel, then use fallback-filter to choose a result. So seeing a fallback resolver on a test page doesn't necessarily mean the primary failed or that a request bypassed the proxy.

To simplify troubleshooting, leave out fallback at first. Once you've verified the basic DNS path, add the following fields to the same dns block if you need to filter unexpected addresses. This example disables GeoIP checks and demonstrates using explicit IP ranges to select fallback results; it won't detect every poisoned response.

  fallback:
    - https://9.9.9.9/dns-query
  fallback-filter:
    geoip: false
    ipcidr:
      - 0.0.0.0/32
      - 240.0.0.0/4

If you enable fallback-filter.geoip and set geoip-code: CN, whether the primary result is considered to belong to the specified region affects which result is selected. This filters answers; it does not control DNS egress. An outdated database, changing CDN address ownership, or deployments across multiple regions can affect the result. GeoIP filtering is not a universal DNS leak protection switch.

nameserver-policy: Choose Which Resolver Handles a Domain

nameserver-policy selects a resolver based on the domain. For example, the snippet below sends example.com and its subdomains to a specified DoH server. This domain is for illustrating the syntax; use a domain that actually needs a dedicated resolver, then verify the merged configuration.

  nameserver-policy:
    "+.example.com":
      - https://1.1.1.1/dns-query

DNS resolution policies and connection routing rules make separate decisions. Selecting a resolver doesn't route that website's traffic through a proxy; likewise, routing a website through a proxy doesn't guarantee its DNS connection uses the proxy. Corporate intranets and home devices may need a local resolver. If you deliberately configure direct DNS for those domains, document them as exceptions and keep them separate from tests for public domains.

TUN DNS Hijacking: Route System Queries on Port 53

A system proxy mainly affects applications that honor proxy settings and usually doesn't change the system DNS path. TUN routes more IP traffic through the core, while DNS hijacking captures ordinary DNS queries. This is still a snippet to merge into your configuration. You need a mihomo client with TUN support and the platform permissions required to create a virtual network interface and modify routes.

tun:
  enable: true
  stack: mixed
  auto-route: true
  auto-detect-interface: true
  dns-hijack:
    - any:53
    - tcp://any:53
  • auto-route sets up routes automatically. After enabling it, confirm that the client reports TUN as running—not just that the switch is on.
  • any:53 and tcp://any:53 cover ordinary UDP and TCP DNS requests that enter the routed path, respectively.
  • auto-detect-interface helps identify the outbound network interface. If you use multiple VPNs, virtual interfaces, or custom routes, verify which interface was actually selected.

DNS hijacking doesn't capture every encrypted DNS request. Apps that use DoH send it over HTTPS; DoT typically uses TCP port 853. Listing port 53 won't redirect either to the core's DNS. Requests handled by another VPN, bypassing excluded routes, or using a special system resolver path may also miss these hijack rules.

Check Browser Secure DNS and IPv6 Separately

For testing, you can temporarily disable the browser's own Secure DNS so it uses the system DNS path. In Chrome, check Settings → Privacy and security → Security → Use secure DNS. The exact labels may vary by version. If you need to keep browser DoH enabled, test it as a separate path, including its resolver, connection egress, and fallback behavior.

Check IPv6 too. Receiving AAAA records from DNS doesn't mean IPv6 traffic is routed correctly. Setting dns.ipv6: false affects how the core handles AAAA responses; it doesn't control system IPv6 routes, and app-specific DNS may still return IPv6 addresses. Don't end testing after checking only the IPv4 egress.

Verify the Fix and Troubleshoot Common Issues

Verify the Results with Three Layers of Evidence

  1. Entry-point evidence: Queries to local port 1053 work, and fake-ip mappings look as expected. With TUN enabled, ordinary system DNS queries reach the core.
  2. Routing evidence: Check the resolver address, matched policy, and connection egress in the client's connection panel or debug logs. Restore your usual log level after debugging, and remove domain, node, and subscription details before sharing logs.
  3. External evidence: Rerun both the Standard and Extended tests. Confirm that every resolver shown is accounted for by your current configuration, then test your browser, command-line tools, and any other applications you need to protect.

You can also capture traffic on the actual outbound interface and use the Wireshark display filter udp.port == 53 || tcp.port == 53 || tcp.port == 853 to look for ordinary DNS and common DoT connections. Choose the right capture interface: plaintext requests on a virtual interface may subsequently be handled by the core, so they aren't necessarily leaking through the physical interface. DoH is carried over HTTPS, and this filter can't show its query contents.

After enabling fake-ip, tests still show my ISP's DNS. What should I check first?

First, use local port 1053 to confirm the DNS configuration has loaded, then verify that system queries enter TUN. Next, check for browser-specific DNS, other VPNs, local resolvers in your policies, and subscription updates that may have overwritten your changes. fake-ip doesn't choose the system's DNS entry point for you.

All websites stopped loading after I configured DoH. How do I recover?

Restore the configuration from before your changes and confirm that your node and basic network connection work. Then enable just one reachable DoH resolver and check for timeouts, TLS errors, or a loop when resolving the node's hostname. Don't add fallback, resolver policies, or rule-based routing at the same time.

My fix stopped working after a subscription update. How do I keep my settings?

Use the client's supported overrides, configuration merging, or configuration enhancement features instead of editing a downloaded subscription file once. After saving, inspect the final configuration and confirm that later merges haven't overwritten the dns and tun fields. Feature names vary by client.

Success doesn't mean a test page shows only one address. It means every DNS path has a clear source and egress. Once verified, restore browser Secure DNS, local network exceptions, and other networking tools one at a time, repeating the tests after each change. That helps identify new paths instead of guessing when something breaks again.