Networking · Clash Meta · DNS

Clash Meta DNS Module:
A Config You Can Actually Understand

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.

01 — Opening

The Goal

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.

02 — Background

Why Clash Needs Its Own DNS

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.

03 — Core Concept

fake-ip: Speed at a Cost

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 Cost

The trick works because Clash sits in the middle and controls both ends. But some domains bypass that expectation entirely:

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.

04 — Exception Handling

Protecting Local Services: 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.

What Belongs on This List

There are three categories of domains that need real IPs:

CategoryWhy fake IP breaks it
*.lan, *.local, *.arpaLocal network — a 198.18.x.x address will never reach your router or NAS
time.*.com, ntp.*.comNTP has no hostname negotiation layer — it just connects to the IP and expects a time server
*.msftncsi.com, msftconnecttest.comOS 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
Practical note: You don't need to be exhaustive here. The wildcard patterns cover the broad categories. App-specific entries are worth adding only when you've actually observed breakage. Treat this list as something you grow over time.
05 — Domain Recovery

Recovering Lost Domain Names: Sniffer

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.

How It Works

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:

TLS ClientHello └── SNI: google.com ← readable by anyone, including Clash

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 and parse-pure-ip

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.

Why This Is Non-Negotiable in fake-ip Mode

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.

Without sniffer enabled in fake-ip mode, your carefully written domain rules do nothing — all routing falls back to IP-based matching only.
06 — Architecture

The DNS Routing Pipeline

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:

DNS query arrives 1. nameserver-policy ← highest priority, checked first (no match) 2. nameserver ← default resolver (with fallback configured) 3. fallback ← concurrent with nameserver, used as correction

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.

07 — Layer 1

Solving the Domestic/Foreign Split: nameserver-policy

The Pollution Problem

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.

How nameserver-policy Works

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

Unpacking geosite:cn,private

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.

Multiple Servers in One Policy Entry

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.

08 — Layer 2

Foreign Domains: Delegating to the Proxy

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.

The # Proxy Suffix

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.

What Happens on the Proxy Side

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.

09 — Bootstrapping

The Bootstrap Problem

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: Resolving DNS Server Hostnames

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: Resolving Proxy Node Hostnames

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'
Key insight: Both fields exist for the same reason — some DNS resolution has to happen before your main DNS pipeline is operational. These are the escape hatches that make the rest of the config possible.
10 — Direct Connections

Direct Connection Re-resolution

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'

When direct-nameserver Kicks In

App queries baidu.com Clash's DNS module intercepts the query nameserver-policy matches geosite:cn → 223.5.5.5 designated (background) Clash immediately returns fake IP 198.18.0.5 to the app App connects to 198.18.0.5 Routing rule matches: DIRECT Clash needs real IP to actually connect direct-nameserver re-resolves baidu.com → gets real IP Connection established

direct-nameserver-follow-policy

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.

11 — Safety Net

Fallback: The Safety Net

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.

What Fallback Is For

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:

nameserver and fallback queried concurrently fallback-filter evaluates nameserver's result: ├── Result looks clean (CN IP for a CN domain) use nameserver result └── Result looks polluted (non-CN IP, suspicious range) use fallback result

Why It's Redundant in Your Setup

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.

The Forced Config Situation

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.

12 — Complete Config

The Complete Config

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

Two Things to Customize

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 DNS223.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.

13 — Closing

Every Line Has a Reason

A good DNS config isn't magic — it's a pipeline where each layer answers a specific question.

How do I keep domain names visible to Clash?
→ Sniffer
How do I make connections fast?
→ fake-ip
Which domains can't handle fake IPs?
→ fake-ip-filter
How do I route domestic and foreign queries to the right DNS?
→ nameserver-policy
How do I resolve foreign domains cleanly without leaking queries?
→ nameserver with #RULES
How do I bootstrap the pipeline before it exists?
→ default-nameserver, proxy-server-nameserver
How do I get real IPs for direct connections?
→ direct-nameserver

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.