The Four Stages of DNS Configuration
When troubleshooting Clash DNS, separate query interception, resolution mode, upstream selection, and connection routing. The system, browser, and interception method determine which DNS service receives an app's queries. Once Clash receives a query, it processes it according to the dns configuration. The proxy mode and rules determine the connection's final route—not the DNS server address alone.
| Stage | Key fields | What to check |
|---|---|---|
| Query interception | listen、tun.dns-hijack | Whether app queries reach the core |
| Resolution mode | enhanced-mode | Whether the response is a real or mapped address |
| Upstream selection | nameserver、fallback-filter | Which set of DNS results is used |
| Connection routing | rules、proxy-groups | Which route the connection actually takes |
The subscription source, the client's editor, and the final runtime configuration may be three different files. If subscription updates overwrite the source, use the client's override or merge feature to preserve your changes. Check the merged result: don't define dns: or tun: twice in the same YAML mapping.
nameserver, fallback, and bootstrap DNS
nameserver: a regular upstream, not a proxy node
nameserver is a list of DNS upstreams. You can enter standard DNS addresses or DoH and DoT addresses supported by the core. A standard address such as 223.5.5.5 typically uses UDP port 53; https://1.1.1.1/dns-query uses DoH, typically over HTTPS on port 443. The upstream must be reachable on your network; the protocol alone doesn't guarantee a successful request.
dns:
enable: true
listen: 127.0.0.1:1053
ipv6: false
enhanced-mode: redir-host
default-nameserver:
- 223.5.5.5
- 119.29.29.29
nameserver:
- 223.5.5.5
- 119.29.29.29
fallback:
- https://1.1.1.1/dns-query
fallback-filter:
geoip: true
geoip-code: CN
ipcidr:
- 240.0.0.0/4
This snippet illustrates how the fields relate; it is not a complete client configuration. 127.0.0.1:1053 listens only on the local machine, making it useful for explicit query tests. The operating system won't automatically send all DNS requests to it just because the port is listening. ipv6: false controls AAAA responses from this DNS module; it does not disable IPv6 in the operating system or block all IPv6 connections.
default-nameserver: resolving upstream hostnames
If an upstream is specified by hostname, such as https://dns.example.com/dns-query, its IP address must be resolved before a connection can be made. default-nameserver handles this bootstrap lookup. The example uses literal IP addresses to avoid a bootstrap loop. It is not the default server for every domain lookup, nor is it a resolver reserved for proxy nodes.
mihomo also provides proxy-server-nameserver for resolving proxy node hostnames. Check this layer separately when a node address is a hostname and the resolution route creates a dependency loop. A website hostname failing to resolve and a node hostname failing to resolve are different problems.
fallback: an alternative result, not a sequential retry chain
With fallback configured, a common query flow sends requests to the primary and fallback groups in parallel, then selects a result based on the filter conditions. It doesn't mean Clash waits for the nameserver to time out before trying each fallback in sequence. Response races, caching, and failure handling depend on the core implementation; list order does not necessarily indicate strict priority.
DoH means only that the connection to the DNS resolver uses HTTPS. It does not automatically route traffic through a proxy, nor does it prevent browsers from using their own secure DNS. Check the core fields, final configuration, and logs to determine whether DNS requests follow routing rules.
fallback-filter: when to use the fallback result
fallback-filter does not change an IP address's geographic location; it sets conditions for choosing the fallback result. In the configuration above, geoip: true with geoip-code: CN means the fallback group is preferred when addresses returned by the primary group don't meet the specified region criteria. This depends on GeoIP data. Outdated data, changes in CDN routing, or inaccurate IP geolocation can all affect the result.
geoip: false: disables this geographic lookup; it does not turn off all filter conditions.ipcidr: use the fallback result when the primary group's response falls within one of these address ranges.240.0.0.0/4is a common example of a reserved range to filter.domain: use the fallback group directly for matching domains, regardless of whether the primary group's response meets the geographic criteria.
fallback-filter:
geoip: false
ipcidr:
- 240.0.0.0/4
domain:
- '+.example.com'
Add this snippet under the existing dns section to replace the earlier fallback-filter. example.com is a reserved documentation domain, not a recommended list of real-world sites. This example disables geographic checks and keeps only the IP range and domain conditions to help isolate issues. Don't filter every private IP range without testing: internal business domains, router admin pages, and home NAS devices may need to resolve to private addresses.
Another common misconception is that fallback-filter.domain acts like a proxy rule. It only controls the source of DNS results; it does not create a DOMAIN-SUFFIX rule. Even if a site gets its address from the fallback resolver, its connection may still match a DIRECT rule.
fake-IP vs. redir-host
fake-IP: create a domain-to-IP mapping
enhanced-mode: fake-ip returns a mapped address for eligible queries. When an app connects to that address, the core can recover the original domain and apply the appropriate rules and connection handling. So seeing 198.18.x.x in a command-line lookup doesn't necessarily mean DNS has been poisoned; it may be the mapping configured on your device.
dns:
enable: true
listen: 127.0.0.1:1053
ipv6: false
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
fake-ip-filter:
- '*.lan'
- '*.local'
default-nameserver:
- 223.5.5.5
nameserver:
- https://1.1.1.1/dns-query
198.18.0.1/16 is in the benchmarking address space. Check local routes before using it to avoid conflicts with test networks. After a mapped address is returned, the corresponding connection must still reach the core. If DNS goes through Clash but traffic bypasses interception, the app may not be able to connect to those addresses.
fake-ip-filter lets specified domains return their real DNS results. The example above uses the usual blacklist filtering behavior and doesn't configure another filter mode at the same time. Add internal services by their actual domain names as needed. However, .local is commonly handled by mDNS; adding it to the filter list does not mean Clash will proxy all multicast discovery traffic.
redir-host: return real IP addresses
redir-host returns the resolved IP address and uses DNS records to associate domains with connections. It can help with compatibility when an app requires real IPs, but domain associations may be incomplete when multiple domains share an address or an app resolves or caches addresses on its own. Neither mode is the same as sniffing: mihomo's sniffer identifies domains from subsequent connection details through a separate mechanism.
TUN dns-hijack: route DNS queries through the core
dns-hijack belongs under tun, not dns. A common mihomo configuration looks like this. When combining it with the DNS snippet in the previous section, keep both top-level fields at the same indentation level.
tun:
enable: true
stack: mixed
auto-route: true
auto-detect-interface: true
dns-hijack:
- any:53
- tcp://any:53
any:53 intercepts regular UDP port 53 queries entering TUN; tcp://any:53 adds TCP port 53. The core's DNS module handles intercepted queries, so the system does not need to explicitly use 127.0.0.1:1053 as its DNS server. The packets must still pass through the TUN route, and the platform must support the relevant routing and interception features.
- Enabling TUN typically requires administrator privileges or system authorization. After the virtual network interface is created, check that its routes are active.
- Browser DoH uses HTTPS, while Android Private DNS commonly uses DoT. Neither is a standard port 53 query, so the two settings above won't automatically redirect it.
- Enabling the system HTTP proxy alone does not guarantee that an app's DNS queries reach the core. Some apps resolve domains through system DNS before opening a proxy connection.
- If multiple VPNs, virtual network interfaces, or local DNS services are running, test with just one interception path first to avoid competing route changes.
Don't change dns.listen to 0.0.0.0:53 just to make interception easier. This changes which interfaces accept connections and may conflict with the system resolver on port 53. Only configure a network-accessible listener if you need to provide DNS to local network devices, and restrict the permitted sources and networks with firewall rules.
Common setups and configuration choices
| Use case | Suggested starting point | What to verify |
|---|---|---|
| Troubleshooting upstream resolution | redir-host + local listener on port 1053 | Whether an explicit query returns a real IP |
| Stable domain-based routing | fake-IP + TUN interception | Whether connections to mapped addresses reach the core |
| Business network or home network with many internal services | Per-domain DNS upstreams and filter exceptions | Whether internal domains stay off public resolvers |
| Comparing two sets of DNS results | nameserver + fallback + explicit filter conditions | Whether the selected result meets the expected conditions |
mihomo can use nameserver-policy to assign an upstream to specific domains. For example, if your company DNS server is at 192.168.50.1 and internal domains use the documentation suffix corp.example, add this under the existing dns section:
nameserver-policy:
'+.corp.example':
- 192.168.50.1
For real deployments, replace these with your actual internal suffix and a reachable DNS server, then check whether the relevant domains also need to be added to fake-ip-filter. Domain policies, fallback filters, and connection rules operate at different layers. When troubleshooting, remove overlapping policies first, verify basic resolution, then add them back one at a time.
If DNS connections should follow routing rules, plan mihomo's respect-rules setting alongside options such as proxy-server-nameserver to avoid a loop where resolving a proxy node requires connecting to that node first. Don't enable this option and add several per-domain upstream policies all at once before the basic configuration is verified.
Verification steps and troubleshooting
Step 1: Check the configuration and listener
Use the matching mihomo executable to check the syntax, then reload the configuration in your client. The commands below assume the current directory contains the active config.yaml and that dig is installed:
mihomo -t -f config.yaml
dig @127.0.0.1 -p 1053 example.com A
dig @127.0.0.1 -p 1053 example.com A +tcp
A successful syntax check doesn't mean every upstream is reachable. If UDP queries work but TCP queries fail, check the listener and local firewall first. If both time out, check dns.enable, port conflicts, and the final runtime configuration. In fake-IP mode, getting a mapped address only confirms that local mapping works; it doesn't prove that the real upstream is healthy.
Step 2: Check the system's DNS path
Specifying 127.0.0.1 and a port explicitly tests the core's listener. Then run nslookup example.com to test the system's default resolution path. Different results don't necessarily indicate a problem, but if you expect every query to go through the core, check system DNS settings, TUN routes, and your browser's secure DNS settings.
Step 3: Check the connection and route
- Clear the cache, close old tabs or app connections, then try again.
- In the client’s connection list, check the target domain, matched rule, and final policy group.
- If DNS resolves successfully but the connection times out, check node reachability, the proxy protocol, and the destination port. Don't switch DNS servers straight away.
- If only internal services fail, query both internal and public DNS to check whether a per-domain upstream policy is missing.
- If you suspect DNS is bypassing Clash, inspect traffic leaving the physical network interface for UDP/TCP ports 53 and 853, as well as connections to known DoH servers. The resolver location shown by a public test site alone cannot prove the request path on your device.
Change one layer at a time: first confirm the core can resolve domains, then check that app queries reach it, and finally inspect the connection rules. This helps distinguish unreachable upstreams, incorrect filter conditions, fake-IP connections bypassing interception, and TUN not intercepting traffic. For full field context, see the configuration reference. For installation and subscription import steps, see the user guide.