By the end, you'll have a complete, working DNS config — and you'll know what every line does and why it's there. No cargo-culting.
If you've ever copy-pasted a Clash Meta DNS config from someone's dotfiles or a forum post, you're not alone. The DNS module has a lot of knobs, and most guides either skip the reasoning entirely or drown you in edge cases before you understand the basics.
This article takes a different approach. By the end, you'll have a complete, working DNS config — but more importantly, you'll know what every line does and why it's there. No cargo-culting.
The goal we're optimizing for is simple: domestic domains resolved by domestic DNS, foreign domains resolved cleanly through your proxy, and local services that don't break. Privacy preserved, speed maintained, routing correct.
Before diving into configuration, it's worth understanding why Clash Meta has a DNS module at all. Normal DNS is simple: your app asks the OS, the OS asks a DNS server, the server returns an IP. Done.
That breaks down the moment you introduce a proxy — specifically TUN mode.
TUN works by creating a virtual network interface that captures all outgoing traffic at the IP packet level. This is powerful: nothing escapes, every connection goes through Clash. But it comes with a fundamental problem. IP packets only carry a destination IP address, not a domain name. By the time a packet hits TUN, the domain name is already gone.
This matters because your routing rules are written in terms of domains:
rules: - DOMAIN-SUFFIX,google.com,Proxy - DOMAIN-SUFFIX,baidu.com,DIRECT
If Clash only sees 142.250.80.46:443, these rules are useless. It has no idea that
IP belongs to Google.
Clash's DNS module solves this by intercepting DNS queries before they leave your machine. Instead of letting the OS resolve domains through system DNS — where Clash never sees the domain name — Clash handles resolution itself, keeping the domain-to-IP mapping so it can make correct routing decisions later.
This is why the DNS module exists. It's not just about picking a fast DNS server — it's about keeping domain names visible to Clash long enough for routing to work correctly.
Clash has two DNS processing modes, controlled by enhanced-mode.
The simpler one is redir-host. When an app queries a domain, Clash resolves it
normally, returns the real IP, and records the mapping — 142.250.80.46 → google.com —
so it can recover the domain name when the connection arrives later. It's straightforward, but it
means every connection waits for a full DNS round trip before it can start.
fake-ip is the smarter alternative. Instead of resolving the domain first, Clash
instantly returns a fake IP from a reserved range — say 198.18.0.5 — and your app
immediately starts connecting. Clash intercepts that connection, recognizes 198.18.0.5
maps to google.com, and routes it correctly. Real DNS resolution happens in the
background, or through the proxy itself.
The result is that connections feel faster — the DNS step is no longer blocking.
enhanced-mode: fake-ip fake-ip-range: '198.18.0.1/16' # the pool of fake IPs Clash draws from
The trick works because Clash sits in the middle and controls both ends. But some domains bypass that expectation entirely:
time.windows.com, get a fake IP, and try to sync time with it — which obviously fails.
For these domains, fake-ip causes silent, hard-to-debug breakage. The connection appears to
succeed at the TCP level but fails at the application level. The solution is the next piece:
fake-ip-filter.
fake-ip-filter is an exception list. Domains matching it bypass the fake-ip
mechanism entirely and get real IP responses instead.
fake-ip-filter-mode: blacklist # everything gets fake IPs, EXCEPT the list below fake-ip-filter: - '*.lan' # LAN hostnames — your router, NAS, printer - '*.local' # mDNS / local services - '*.arpa' # reverse DNS lookups — inherently IP-based - 'time.*.com' # NTP time servers - 'ntp.*.com' # NTP time servers - '+.market.xiaomi.com' # Xiaomi app store CDN - 'localhost.ptlogin2.qq.com' # QQ local authentication - '*.msftncsi.com' # Windows network connectivity check - 'www.msftconnecttest.com' # Windows network connectivity check
The blacklist mode means the rule is simple: everything gets fake IPs,
except domains on this list. Those get real IPs.
There are three categories of domains that need real IPs:
| Category | Why fake IP breaks it |
|---|---|
| *.lan, *.local, *.arpa | Local network — a 198.18.x.x address will never reach your router or NAS |
| time.*.com, ntp.*.com | NTP has no hostname negotiation layer — it just connects to the IP and expects a time server |
| *.msftncsi.com, msftconnecttest.com | OS connectivity probes — fake IP makes Windows conclude you're behind a captive portal |
| App-specific (QQ, Xiaomi) | Apps that authenticate against localhost or need CDN-specific IPs |
We established that TUN mode loses domain names — Clash only sees IP packets. The DNS module partially solves this by intercepting queries and maintaining a fake-ip mapping. But there's a gap.
Not all traffic arrives with a DNS query Clash can intercept. Some apps cache DNS results across
restarts. Some connect directly by IP. And in fake-ip mode specifically, the destination IP is
always fake — doing a GeoIP lookup on 198.18.0.5 tells you nothing useful.
Sniffer closes this gap by recovering domain names from the traffic itself.
Even though IP packets carry no domain name, the application-layer protocols inside almost always do. When your browser opens a TLS connection, the very first packet it sends is a ClientHello — and embedded in that handshake, in plaintext, is the SNI (Server Name Indication) field:
HTTP is even more explicit, putting the domain in the Host: header of every request.
QUIC follows the same pattern as TLS. Sniffer intercepts these handshake packets, extracts the
domain name, and substitutes it back as the connection's target. From that point on, Clash treats
the connection as if it arrived with google.com as the destination — and your
domain-based routing rules fire correctly.
sniffer: enable: true force-dns-mapping: true # override stale redir-host mappings with sniffed SNI parse-pure-ip: true # sniff connections with no DNS mapping at all override-destination: true sniff: TLS: ports: [443, 8443] HTTP: ports: [80, 8080-8880] QUIC: ports: [443, 8443]
force-dns-mapping handles the case where Clash already has a domain-to-IP mapping from DNS interception (redir-host mode), but the sniffed SNI is more accurate — perhaps a more specific subdomain, or a fresher result than what's in the cache. The sniffed SNI overwrites it.
parse-pure-ip handles connections that arrive with no DNS mapping at all — apps that cached an IP, or services that connect directly by IP. Without this, Clash would have no domain to work with. With it, Sniffer still attempts to extract one from the handshake.
In fake-ip mode, every destination IP is from the 198.18.0.0/16 range. GeoIP
rules are meaningless — that range isn't allocated to any country. Domain rules can't fire
from an IP alone. Sniffer is the only mechanism that recovers the real domain
name after it's been replaced by a fake IP.
Now that you understand the individual pieces, let's look at how they fit together. When Clash receives a DNS query, it processes it through a pipeline with three layers, each with a strict priority order:
The rules are simple:
This priority order is the key to building a clean domestic/foreign DNS split — which is exactly what the next three sections configure, one layer at a time.
You might wonder: why not just use a single good DNS server for everything? The problem is that "good" means different things for domestic and foreign domains.
For domestic domains, a foreign DNS like 8.8.8.8 works technically, but returns
suboptimal results — you might get routed to an overseas CDN node instead of a local one,
adding unnecessary latency.
For foreign domains, a domestic DNS is actively harmful. Chinese ISP DNS and
even public domestic resolvers like 223.5.5.5 return polluted results for blocked
domains — wrong IPs, dead IPs, or IPs that lead nowhere. You can't trust their answers for
anything that goes through your proxy.
The only clean solution is to route DNS queries themselves based on the domain.
This is exactly what nameserver-policy does.
nameserver-policy is a map of domain patterns to DNS servers. It's checked first,
before anything else in the pipeline. If a domain matches, it goes to the specified server and
the result is used directly — no fallback, no pollution filtering needed.
nameserver-policy: 'geosite:cn,private': - '223.5.5.5' # Alibaba DNS — fast, domestic - '119.29.29.29' # DNSPod — fast, domestic - 'https://doh.pub/dns-query' # DNSPod DoH — encrypted backup - 'https://dns.alidns.com/dns-query' # Alibaba DoH — encrypted backup
The key geosite:cn,private is actually two groups combined:
geosite:cn — a curated list of Chinese domains maintained by the community. Baidu, WeChat, Taobao, and thousands more. Domains matching this go to domestic DNS, which knows their correct CDN topology and returns fast, accurate results.
geosite:private — private and reserved network domains: *.local,
*.lan, *.arpa, reverse DNS, and similar. These should never leave
your local network. Sending them to a foreign DNS server would both fail and leak information
about your internal network.
Notice the policy entry lists four servers. Clash queries all of them and takes the fastest response. The plain UDP servers respond quickly for most queries. The DoH servers are slower but encrypted — they serve as a reliable backup when plain UDP is unreliable or when you want query privacy even for domestic lookups.
Domains that don't match nameserver-policy fall through to nameserver.
At this point in the pipeline, we're dealing almost exclusively with foreign domains — anything
domestic was already caught by geosite:cn,private.
For foreign domains, we have a clear requirement: the DNS query itself must travel through the proxy. Two reasons:
Pollution — querying 8.8.8.8 directly from mainland China is
unreliable at best, actively poisoned at worst. The query needs to exit through your proxy
tunnel to reach the DNS server cleanly.
Privacy — a DNS query sent in plaintext over your ISP's network reveals every domain you visit, even if the actual connection goes through a proxy. Routing the query through the proxy closes that leak.
Clash lets you attach a routing instruction directly to a DNS server entry using the
# suffix:
nameserver: - 'https://8.8.8.8/dns-query#ProxyGroupName' # through a specific proxy group - 'https://1.1.1.1/dns-query#RULES' # follow routing rules
#ProxyGroupName sends the DNS query through a named proxy group — explicit
and predictable. Replace ProxyGroupName with your actual proxy group name.
#RULES tells Clash to route the DNS query according to your routing rules,
the same way regular traffic is routed. Since 8.8.8.8 and 1.1.1.1
are foreign IPs, they'll naturally hit your proxy rule and go through the tunnel anyway.
Functionally equivalent to #ProxyGroupName for most setups, and slightly more
flexible if your proxy group name ever changes.
When a foreign domain query travels through the proxy, the proxy server resolves it on your behalf — from its own location, outside China. The result is clean, unpolluted, and reflects the correct CDN topology for a foreign user. In fake-ip mode, this often happens transparently — Clash sends the domain name directly to the proxy, and the proxy handles resolution entirely on its end.
There's a chicken-and-egg problem hiding in the config so far.
We configured nameserver-policy to use DoH servers like
https://doh.pub/dns-query — but DoH is HTTPS, and HTTPS requires a hostname to
be resolved before the connection can be made. Who resolves doh.pub?
Similarly, your proxy nodes are defined by hostnames like jp.example.com. Clash
needs to resolve that hostname to connect to your proxy — but your DNS is configured to route
queries through the proxy. You can't use the proxy to resolve the address you need to
reach the proxy.
Two dedicated fields solve these two problems independently.
default-nameserver is a bootstrap resolver used exclusively to resolve the
hostnames of your other DNS servers. It runs before the rest of the pipeline exists, so it
has one hard constraint: it must be plain IP addresses, not hostnames.
default-nameserver: - '223.5.5.5' # plain IP — no resolution needed to reach this - '119.29.29.29' # plain IP — same
These are queried only for DNS infrastructure setup — resolving doh.pub,
dns.alidns.com, and similar. They never handle your actual traffic queries.
proxy-server-nameserver is used exclusively to resolve the domain names of your
proxy nodes. It runs independently of the main DNS pipeline, specifically to break the
circular dependency.
proxy-server-nameserver: - '223.5.5.5' - '119.29.29.29'
When a connection is routed DIRECT — not through the proxy — Clash needs to
make an actual TCP connection to a real IP address. In fake-ip mode, the IP it has is fake.
It needs to re-resolve the domain to get a real, routable IP before it can establish
the connection. This is what direct-nameserver is for.
direct-nameserver: - '223.5.5.5' - '119.29.29.29'
This option controls whether direct-nameserver respects your
nameserver-policy during re-resolution:
direct-nameserver-follow-policy: true
With true, re-resolution for direct connections follows the same policy routing
as initial queries — domestic domains use domestic DNS, everything is consistent.
In practice, your DIRECT connections are almost entirely domestic domains — geosite:cn
traffic that's already covered by policy. So this setting is mostly defensive hygiene:
it keeps the pipeline behavior consistent end-to-end even if your routing rules change in the future.
It costs nothing to set it to true.
We've deliberately left fallback for last, because with a well-configured
nameserver-policy handling the domestic/foreign split, fallback is largely redundant.
But it's worth understanding what it does and when it still makes sense.
Fallback exists to correct DNS pollution. When fallback is configured, Clash
queries both nameserver and fallback concurrently. Then
fallback-filter examines the nameserver result and decides which
one to actually use:
Your nameserver-policy already routes queries to the correct DNS server
before any resolution happens. Domestic domains go to domestic DNS, foreign
domains go through the proxy. There's no opportunity for pollution to enter the pipeline.
Fallback's pollution-correction mechanism solves a problem that nameserver-policy
prevents from occurring in the first place.
Clash Verge's GUI forces certain fallback-filter fields to be present even when
you don't want them. The way to neutralize them completely:
fallback: [] # empty — fallback-filter has nothing to work with fallback-filter: geoip: false # disable GeoIP evaluation entirely
With fallback: [], the filter settings are completely inert — they're just
decoration. The pipeline behaves as if fallback doesn't exist.
Every option below has been covered in the sections above. The comments trace each line back to its reasoning.
# ── Sniffer ────────────────────────────────────────────────────────────────── # Recovers domain names from TLS/HTTP handshakes in TUN mode. # Without this, domain-based routing rules are useless for TUN traffic. sniffer: enable: true force-dns-mapping: true # override stale redir-host mappings with sniffed SNI parse-pure-ip: true # sniff connections with no DNS mapping at all override-destination: true sniff: TLS: ports: [443, 8443] HTTP: ports: [80, 8080-8880] QUIC: ports: [443, 8443] # ── DNS Module ─────────────────────────────────────────────────────────────── dns: enable: true listen: ':53' # fake-ip: return fake IPs immediately for speed. # Clash intercepts the connection and routes by domain; real resolution # happens in the background or through the proxy. enhanced-mode: fake-ip fake-ip-range: '198.18.0.1/16' # Blacklist mode: everything gets fake IPs, except domains on this list. fake-ip-filter-mode: blacklist fake-ip-filter: - '*.lan' # LAN hostnames — router, NAS, printer - '*.local' # mDNS / local services - '*.arpa' # reverse DNS — inherently IP-based - 'time.*.com' # NTP time servers - 'ntp.*.com' # NTP time servers - '+.market.xiaomi.com' # Xiaomi app store CDN - 'localhost.ptlogin2.qq.com' # QQ local authentication - '*.msftncsi.com' # Windows network connectivity check - 'www.msftconnecttest.com' # Windows network connectivity check # Bootstrap DNS: resolves DoH server hostnames before the pipeline exists. # Must be plain IPs — no hostname resolution available at this stage. default-nameserver: - '223.5.5.5' - '119.29.29.29' # Resolves proxy node hostnames independently of the main pipeline. # Breaks the circular dependency: proxy needs DNS, DNS needs proxy. proxy-server-nameserver: - '223.5.5.5' - '119.29.29.29' # Highest priority layer: routes queries by domain before any resolution. # CN and private domains → domestic DNS. Never touches foreign DNS servers. nameserver-policy: 'geosite:cn,private': - '223.5.5.5' # Alibaba DNS — fast, domestic - '119.29.29.29' # DNSPod — fast, domestic - 'https://doh.pub/dns-query' # DNSPod DoH — encrypted backup - 'https://dns.alidns.com/dns-query' # Alibaba DoH — encrypted backup # Default layer: handles everything not caught by nameserver-policy. # In practice, almost exclusively foreign domains at this point. # #RULES routes the query through the proxy tunnel — clean, unpolluted result. nameserver: - 'https://8.8.8.8/dns-query#RULES' - 'https://1.1.1.1/dns-query#RULES' # Re-resolves domains for direct connections — fake IPs can't be used # to establish a real connection; a real IP is needed at this stage. direct-nameserver: - '223.5.5.5' - '119.29.29.29' # Respect nameserver-policy during re-resolution — keeps behavior consistent # end-to-end. Defensive option; DIRECT traffic is almost always domestic. direct-nameserver-follow-policy: true # Fallback disabled — nameserver-policy handles the split cleanly upfront. # fallback: [] and geoip: false neutralize any GUI-forced filter fields. fallback: [] fallback-filter: geoip: false prefer-h3: false respect-rules: false use-hosts: false use-system-hosts: false ipv6: true
Proxy group name — if you use #ProxyGroupName instead of
#RULES on the nameserver entries, replace it with your actual proxy group name,
e.g. Proxy.
ISP DNS vs public domestic DNS — 223.5.5.5 and
119.29.29.29 are public domestic DNS servers, not your ISP's DNS specifically.
If you want your literal ISP DNS, use system instead. Public domestic DNS is
generally more reliable and has better CDN topology awareness, so the distinction rarely
matters in practice.
A good DNS config isn't magic — it's a pipeline where each layer answers a specific question.
Once you see it that way, the config stops being a wall of options to copy-paste and becomes a set of deliberate answers to deliberate questions. If your network environment changes — a new proxy group, a different ISP, additional local services — you'll know exactly which part of the config to touch and why.