Clash Advanced Configuration Handbook

This is the most information-dense page on the site, built as a systematic reference: organized by topic, it covers the principles and parameters behind proxy groups, rule providers, DNS, TUN and Fake-IP, domain sniffing, local overrides, and the external controller. If you just want the client running in ten minutes, start with the quick start guide for the main workflow; come back here for any given topic once you want to understand "why is it configured this way, and what else can I do."

Organized around mihomo core config syntax 8 chapters Includes working YAML examples
Handbook — 00_basics.yaml

Config Basics: File Structure and How Changes Take Effect

Everything in the next seven chapters ultimately lands in the same YAML config file. Before diving in, spend a few minutes understanding where the core's config actually comes from, what parts it's assembled from, and how to confirm a change has taken effect. This chapter is the foundation for the whole handbook — skip it and edit configs directly, and you'll likely run into "I changed it but nothing happened" or "my subscription updated and wiped all my customizations."

Where config files come from and how they layer

In daily use, the config the core actually loads is usually not a single file but the merged result of three layers: the bottom layer is the raw YAML pulled from your airport subscription (node list plus the airport's preset groups and rules); the middle layer is local overrides (Merge/Script) provided by the client (Clash Plus, Clash Verge Rev, etc.) to append or replace fields on top of the subscription; the top layer is the switches in the client UI (ports, system proxy, TUN, etc.), some of which clients also fold into the final config. The point of understanding this layering: anything you want to keep long-term should live in the override layer, not be edited directly into the subscription file — the next time the subscription updates, direct edits to the subscription file get overwritten as-is. See Chapter 7 for how overrides work in practice.

A few global fields you can't avoid

These fields sit at the top level of the config file and get referenced repeatedly in later chapters — worth getting familiar with them now:

FieldPurposeCommon values
mixed-portCombined HTTP and SOCKS5 listening port7890
allow-lanWhether other devices on the LAN can connect to this portfalse (recommended default)
modeGlobal operating moderule / global / direct
log-levelLog verbosityinfo; switch to debug temporarily while troubleshooting
ipv6Whether to enable IPv6 resolution and outboundKeep false if your network's IPv6 quality is uncertain

mode deserves a bit more detail: keep it on rule (rule-based routing) day to day; global sends all traffic through a single policy and is only useful for temporarily checking "is this a rule problem"; direct is effectively a temporary proxy disable. All three can be toggled with one click in the client UI, no file edits needed.

Confirming a change took effect

There are two ways to apply a config change: the "Reload Config" button in the client UI (recommended — doesn't interrupt the current session), or restarting the core. The most reliable way to confirm it worked is the log page — on startup or reload the core prints listening ports, TUN status, rule counts, and so on line by line; if there's a syntax error, the reload will fail and the log will point to the offending line. If the log fields don't make sense to you, see this article: How to read Clash runtime logs. One more general tip when editing YAML: always indent with spaces, never tabs — YAML is extremely sensitive to indentation, and a lot of "config failed to load" errors are really just misaligned indentation.

Applies to core: mihomoConfig syntax: YAML
Handbook — 01_proxy_groups.yaml

Proxy Group Types in Practice

Proxy groups (proxy-groups) are the hub of the routing system: when a rule matches, it doesn't point directly at a node — it points at a proxy group, which then decides "which node to use right now." Airport subscriptions usually ship with a preset set of groups, but once you understand how each type behaves, you can redesign the whole thing around your own habits.

How the four common types differ

TypeSelection methodTypical use
selectManual pick, state is rememberedTop-level entry group, or a business group where you want to manually pin a region
url-testPeriodic latency test, auto-picks the fastest nodeAn "auto speed test" group for low everyday latency
fallbackPicks the first available node in list orderPrimary/backup failover: auto-falls back if the main node goes down
load-balanceSpreads connections across multiple nodesMulti-threaded downloads, avoiding a single node getting throttled

Note the core difference between url-test and fallback: the former chases "fastest" and switches whenever the ranking changes; the latter chases "stability" and never switches as long as the top node on the list is still alive. url-test gives a better everyday browsing experience, while scenarios sensitive to connection continuity (video calls, long-lived logins) are better served by fallback.

A reference group configuration

proxy-groups:
  - name: Node Select
    type: select
    proxies: [Auto Test, Failover, HK-01, JP-01, DIRECT]

  - name: Auto Test
    type: url-test
    url: https://www.gstatic.com/generate_204
    interval: 300
    tolerance: 50
    proxies: [HK-01, HK-02, JP-01]

  - name: Failover
    type: fallback
    url: https://www.gstatic.com/generate_204
    interval: 300
    proxies: [HK-01, JP-01, SG-01]

  - name: Downloads
    type: load-balance
    strategy: consistent-hashing
    url: https://www.gstatic.com/generate_204
    interval: 300
    proxies: [HK-01, HK-02, HK-03]

A few key parameters: url is the target address for health checks — convention is to use a lightweight address that returns a 204 status; interval is the check interval in seconds, and shouldn't go below 120, since overly frequent testing itself burns node traffic; tolerance only applies to url-test and means the latency gap between the old and new best node must exceed this many milliseconds before switching, which effectively suppresses two similarly-latent nodes flip-flopping back and forth; load-balance's strategy can be consistent-hashing (same target site always routes through the same node, friendly to login sessions) or round-robin (round-robins per connection, spreads load more thoroughly). You can also add lazy: true to a group so it pauses testing while unused, saving more traffic.

In practice: a three-tier nesting of entry — function — region

Proxy groups can reference other proxy groups — the most useful feature when designing a routing system. The recommended layout is three tiers: a single top-level select entry point (normally parked on "Auto Test"); a middle tier split by function, e.g. "Streaming," "AI Services," "Downloads," each a select with region groups and the top-level entry as candidates; and a bottom tier of region-aggregated url-test groups (HK Auto, JP Auto, etc.). This way no action is needed day to day, and when you want to pin a specific business to a particular region, you just click once in the corresponding function group without affecting other traffic. See the next chapter for how rules point to these function groups.

Related chapter: rule-provider subscriptions
Handbook — 02_rule_providers.yaml

Rule Providers as Subscriptions

Writing thousands of DOMAIN-SUFFIX entries directly into the config file's rules section is hard to maintain and hard to update. Rule providers (rule-providers) split rules into standalone files referenced as subscriptions: the main config only needs one RULE-SET line, and the actual rule content is pulled from a remote source on a schedule and cached locally. Decoupling rule updates from the main config is one of the highest-value upgrades in advanced configuration.

behavior and format: the three "flavors" of rule files

behaviorFile contentMatching overhead
domainPlain domain list (supports the +. wildcard prefix)Low, suited to very large lists
ipcidrPlain IP-range listLow
classicalFull rule statements (can mix multiple rule types)Relatively higher, most flexible

format describes the file format: yaml and text are human-readable text; mrs is mihomo's binary format — loads fast, small footprint, but only supports the domain and ipcidr behaviors and can't be viewed directly in an editor. When referencing someone else's rule repository, the behavior and format must match the actual file content — getting it wrong causes the whole rule set to fail loading.

Declaring and referencing a rule provider

rule-providers:
  streaming:
    type: http
    behavior: classical
    format: yaml
    url: https://example.com/rules/streaming.yaml
    path: ./rules/streaming.yaml
    interval: 86400
  cn-ip:
    type: http
    behavior: ipcidr
    format: mrs
    url: https://example.com/rules/cn-ip.mrs
    path: ./rules/cn-ip.mrs
    interval: 86400

rules:
  - RULE-SET,streaming,Streaming
  - RULE-SET,cn-ip,DIRECT,no-resolve
  - GEOIP,CN,DIRECT
  - MATCH,Node Select

interval is the auto-update interval in seconds; rule lists don't change often, so 86400 (one day) is plenty; path is the local cache location — if a fetch fails, the core falls back to the last cached list, so the core doesn't fail to start just because one provider's endpoint is having issues.

Match order and no-resolve

The rules section is matched top to bottom, stopping at the first hit — order equals priority. A general layout principle: put precise, business-specific rules first; put GEOIP-style IP rules further down — because when an IP rule is evaluated against a domain target, the core first needs a DNS lookup to determine ownership, and putting it early would slow down matching for every request. Adding the no-resolve suffix to IP-type rules means "skip this rule if the target isn't already an IP — don't trigger a resolution just for it," a common technique for controlling resolution overhead. The last rule should always be a catch-all MATCH, otherwise unmatched traffic behaves unpredictably.

RULE-SET requires core support for the rule-set feature
Handbook — 03_dns.yaml

DNS Configuration Tuning

A lot of "the rules are clearly written correctly but routing is still wrong" or "pages are slow on the very first load" problems trace back to DNS. The reason Clash bundles its own DNS module is that routing decisions rely heavily on domain resolution results: a tampered resolution causes IP rules to misjudge; a resolution that goes through a slow path makes every subsequent request queue behind it. This chapter explains what each DNS field does and the standard approach to splitting resolution between domestic and international domains.

Field roles: default-nameserver vs. nameserver

The pair that's easiest to confuse: default-nameserver does exactly one thing — resolve the hostnames of the DoH/DoT servers listed under nameserver themselves (a domain like doh.pub also needs to be resolved to an IP before it can be reached), so it must be filled with plain-IP traditional DNS servers; nameserver is the actual upstream used for everyday lookups, and should point to encrypted DNS (DoH/DoT) to avoid plaintext queries being tampered with along the path. enhanced-mode controls how resolution results are presented (fake-ip or redir-host), and is closely tied to TUN — details in Chapter 4.

Routing different domains to different upstreams: nameserver-policy

dns:
  enable: true
  listen: 0.0.0.0:1053
  enhanced-mode: fake-ip
  fake-ip-range: 198.18.0.1/16
  default-nameserver:
    - 223.5.5.5
    - 119.29.29.29
  nameserver:
    - https://doh.pub/dns-query
    - https://dns.alidns.com/dns-query
  nameserver-policy:
    "geosite:cn":
      - https://doh.pub/dns-query
    "geosite:geolocation-!cn":
      - https://dns.cloudflare.com/dns-query
      - https://dns.google/dns-query

nameserver-policy is the modern replacement for the old fallback + fallback-filter combination: it assigns upstreams directly by domain category — domains inside mainland China go to a domestic public DoH resolver, returning a nearby CDN node for the best speed; domains outside mainland China go to an international DoH resolver, avoiding tampering at the source. The geosite: prefix references the domain classification database bundled with the core — you don't need to maintain your own domain lists. Compared with a fallback mechanism that queries every upstream first and then filters the results, the policy approach has a shorter, more predictable path.

Common pitfalls and how to verify

Pitfall one: setting nameserver entirely to overseas DNS. An overseas upstream resolving a domestic domain will return an overseas-facing CDN node, making direct domestic traffic slower — a frequent cause of "the proxy is on and now even domestic sites lag," and the troubleshooting approach can be found in the slow-speed troubleshooting checklist. Pitfall two: ignoring the browser's built-in DoH — Chrome/Firefox's "Secure DNS" setting bypasses the client's DNS module entirely, breaking both fake-ip and domain rules; it's best to turn it off in browser settings, or rely on domain sniffing as a fallback. The most direct way to verify DNS is behaving as expected is to turn on debug logging and watch which upstream each domain actually uses; for more resolution-related issues, check the troubleshooting category on the FAQ page.

When ipv6: false, the DNS module won't return AAAA records. If your network's IPv6 quality is unstable, leaving it off avoids a batch of hard-to-diagnose "intermittent connectivity" issues; you can turn it on later once you've confirmed IPv6 is healthy on your link.

Recommended mode: fake-ip + nameserver-policy
Handbook — 04_tun_fakeip.yaml

TUN Mode and Fake-IP

In system proxy mode, only apps that "respect the system proxy settings" hand their traffic to the client — browsers mostly comply, but a fair number of desktop programs, command-line tools, and game clients ignore that setting entirely and connect directly. TUN mode takes over all traffic at the network layer by creating a virtual network adapter, folding every app into the routing system whether it cooperates or not — that's its core value.

The minimal config for enabling TUN

tun:
  enable: true
  stack: mixed
  auto-route: true
  auto-detect-interface: true
  dns-hijack:
    - any:53

auto-route automatically configures the system routing table, pointing the default route to the virtual adapter; auto-detect-interface automatically identifies the real physical network interface as the egress, avoiding a loop where traffic spins inside the virtual adapter; dns-hijack hijacks plaintext DNS queries sent to port 53 on any address and redirects them to the core's DNS module, ensuring a single point of resolution — this is essential for fake-ip to work correctly. In practice you usually don't need to write this by hand: clients like Clash Plus and Clash Verge Rev all provide a TUN toggle that injects an equivalent config.

Enabling TUN on Windows requires administrator privileges (creating a virtual adapter and rewriting the routing table are both privileged operations); mainstream clients handle this via elevation or a system service. A UAC prompt the first time you enable it is normal. Clients for every platform are available on the download page.

Three implementations of stack

ValueImplementationCharacteristics
systemReuses the OS network stackGood performance, close to native behavior, depends on OS stack quality
gvisorUser-space network stackStable compatibility, good isolation, slightly lower throughput
mixedTCP via system, UDP via gvisorBest of both, the general default choice

Pick mixed unless you have a specific need; if some app misbehaves under TUN, swapping to a different stack implementation is the standard step for troubleshooting compatibility issues.

Fake-IP: how it works and the exception list

How enhanced-mode: fake-ip works: when an app makes a domain lookup, the DNS module doesn't actually resolve it — it immediately returns a "fake IP" from the reserved 198.18.0.0/16 block and remembers the mapping between that fake IP and the domain; the app connects using the fake IP, the core reverse-looks-up the domain when the connection comes in, routes by domain rule, and only the egress side performs the real resolution when it's actually needed. The upside is direct: it skips a local round-trip waiting for real resolution, and rule matching gets a reliable domain instead of a potentially tampered IP. The alternative redir-host mode returns the real IP — more "traditionally" compatible, but brings resolution latency and tampering risk, and is no longer the recommended default.

The cost of fake-ip: a handful of programs use resolution results for purposes beyond just connecting (time sync, LAN discovery, reporting the IP back to a server), and a fake IP will break them. fake-ip-filter is the exception list — domains on it get a real resolution instead:

dns:
  fake-ip-filter:
    - "*.lan"
    - "+.local"
    - "time.windows.com"
    - "+.ntp.org"
    - "+.stun.*.*"

Typical candidates for the list: NTP time-sync domains, LAN mDNS domains, and STUN/game-matchmaking services that need real address negotiation. If a particular app only misbehaves once TUN + fake-ip is on, suspect it needs to be added to this list first.

fake-ip-range: 198.18.0.1/16 (reserved test block)
Handbook — 05_sniffer.yaml

Domain Sniffing

Most routing rules are written around domains, but some connections reach the core with only an IP and no domain: the app has its own built-in DNS (e.g. a browser with DoH enabled) that bypasses the client's resolution, or the program is simply hardcoded to connect to an IP. Connections like this can only fall back to GEOIP or the catch-all rule, and routing precision suffers badly. Domain sniffing exists to "recover" the domain: it reads the target domain out of plaintext handshake metadata — the TLS SNI field, the HTTP Host header, the QUIC initial packet — and lets that domain participate in rule matching again.

Enabling it and setting a port range

sniffer:
  enable: true
  sniff:
    HTTP:
      ports: [80, 8080-8880]
      override-destination: true
    TLS:
      ports: [443, 8443]
    QUIC:
      ports: [443]
  force-domain:
    - "+.example.com"
  skip-domain:
    - "Mijia Cloud"

Under sniff, port ranges are declared per protocol — sniffing needs to read a connection's first few packets, and limiting it to common ports keeps the overhead negligible. Worth noting: sniffing only reads metadata (SNI, Host) that's already sent in plaintext during the handshake stage — it never decrypts the encrypted payload.

override-destination, force-domain, and skip-domain

override-destination decides whether a sniffed domain replaces the connection's target address: once enabled, subsequent matching and outbound use the sniffed domain, which fixes issues like "the fake-ip mapping expired" or "the app connected to a new service using a stale IP" — generally recommended to enable. force-domain is a forced-sniff list — domains on it get re-sniffed and cross-checked even if there's already a resolution record, useful for sites where the CDN domain often diverges from the actual service domain. skip-domain is a skip list — some device cloud services (like the Mijia example) put non-standard identifiers in the TLS handshake, and sniff-and-replace actually breaks the connection, so they're excluded. Together, these three parameters let you tune exactly how far sniffing reaches.

When you should turn sniffing on

Two signals suggest you need it: first, your log is full of connections whose target is a plain IP, ending up entirely on GEOIP or MATCH; second, you've written domain rules but traffic from certain apps never matches them. Once sniffing is on, going back to the log shows these connections carrying domains again, and rule matching improves immediately. Combined with the fake-ip setup from the previous chapter, sniffing is also the last line of defense once a browser's DoH bypasses the client's DNS. As an aside, if you see browser certificate warnings around the time you enable sniffing, the two are usually unrelated — see this analysis for the actual cause: why HTTPS certificate errors show up after enabling Clash.

Sniffing targets: SNI / Host / QUIC initial packet
Handbook — 06_override_providers.yaml

Local Overrides and Multi-Subscription Merging

Chapter 1 covered the principle: customizations belong in the override layer, not in the raw subscription. This chapter covers two things in detail — how to use a client's override mechanism to persist your changes, and how to merge multiple airport subscriptions into a single group system.

Local overrides: making customizations survive subscription updates

Mainstream clients (Clash Plus, Clash Verge Rev, etc.) all provide an Override capability, usually in two forms: a declarative Merge — a YAML snippet that the client merges in every time the subscription loads; and a programmatic Script — a small function that receives the parsed config object, transforms it, and returns it. Merge suits structured additions like appending a DNS section or prepending a few custom rules; Script suits scenarios that need iteration logic, like "insert one node into every group" or "filter nodes by name." A typical Merge snippet:

dns:
  enable: true
  enhanced-mode: fake-ip

rules:
  - DOMAIN-SUFFIX,internal.example.com,DIRECT
  - PROCESS-NAME,Steam.exe,DIRECT

Different clients implement Merge semantics slightly differently (whole-section replace vs. per-item append, rules prepended vs. appended) — the first time you use it, always preview the merged final config in the client to confirm it matches what you expect before saving. For where the override entry point lives in each client, see the blog's interface walkthrough.

proxy-providers: the right way to merge multiple subscriptions

If you hold multiple airport subscriptions at once, don't switch back and forth between separate config files — declare each subscription as a node provider with proxy-providers, then reference it from proxy groups via the use field, folding all nodes into one shared group and rule system:

proxy-providers:
  airport-a:
    type: http
    url: https://example.com/sub/airport-a
    path: ./providers/airport-a.yaml
    interval: 43200
    health-check:
      enable: true
      url: https://www.gstatic.com/generate_204
      interval: 600
  airport-b:
    type: http
    url: https://example.com/sub/airport-b
    path: ./providers/airport-b.yaml
    interval: 43200
    health-check:
      enable: true
      url: https://www.gstatic.com/generate_204
      interval: 600

proxy-groups:
  - name: All Nodes
    type: select
    use: [airport-a, airport-b]
  - name: HK Auto
    type: url-test
    url: https://www.gstatic.com/generate_204
    interval: 300
    use: [airport-a, airport-b]
    filter: "(?i)Hong ?Kong|HK"

filter uses a regex to filter nodes by name, and is the key to building region groups — Hong Kong nodes from two different airports get automatically collected into the same "HK Auto" group and compete on latency. use and proxies can coexist in the same group — mixing manually added nodes with subscription-sourced ones is perfectly fine.

Update strategy and fault tolerance

interval: 43200 (12 hours) is plenty frequent for a subscription; the path cache ensures that when a fetch fails, the previous node list is reused, so the core doesn't fail entirely just because one airport's endpoint is glitching. It's worth enabling health-check for every provider — otherwise a url-test group has no latency data to work with and can't auto-select. Also note: a provider's url is fetched directly by the core — if the subscription address itself needs a proxy to reach, you'll need to configure a proxy path on the provider or ensure a working direct route exists first, to avoid a chicken-and-egg deadlock.

use and proxies can be mixedfilter supports regex
Handbook — 07_external_controller.yaml

External Controller and Management Panels

The core ships with a RESTful control API (the external controller) — the client's graphical interface is, at bottom, just a consumer of this API. Understanding it unlocks a fair amount: managing a bare core running on a router/server from a browser panel, writing scripts to switch policies on a schedule, or feeding connection data into your own monitoring.

Enabling the interface and access control

external-controller: 127.0.0.1:9090
secret: "your-password"
external-ui: ./ui
external-ui-url: https://example.com/ui.zip

With external-controller listening on 127.0.0.1, only the local machine can access it — the safe default for desktop use; switching it to 0.0.0.0:9090 allows access from other devices on the LAN, and in that case secret must be set and reasonably strong — this API can read connections, change policies, and reload configs, which amounts to full control of the client. Callers pass the secret via an Authorization: Bearer header.

A few commonly used API calls

# List all proxy groups and node status
curl -H "Authorization: Bearer your-password" http://127.0.0.1:9090/proxies

# Switch the "Node Select" group to a given candidate
curl -X PUT -H "Authorization: Bearer your-password" \
  -d '{"name":"HK Auto"}' \
  http://127.0.0.1:9090/proxies/Node%20Select

# View active connections
curl -H "Authorization: Bearer your-password" http://127.0.0.1:9090/connections

# Trigger a latency test for a specific node
curl -H "Authorization: Bearer your-password" \
  "http://127.0.0.1:9090/proxies/HK-01/delay?timeout=5000&url=https://www.gstatic.com/generate_204"

/logs and /traffic are streaming endpoints that continuously push log lines and real-time traffic — the scrolling logs and rate graphs in a panel come straight from these; PATCH /configs lets you switch mode or change ports without a restart.

Web panels: giving a bare core a face

external-ui points to a directory of static web assets, which the core hosts under the /ui path on the control port; external-ui-url makes the core automatically download and unpack a panel bundle. Community panels like metacubexd and yacd (and their forks) offer similar features: group switching, latency tests, connection lists, rule and log viewing. This setup is most useful for "running bare mihomo on a server or router" — core installer packages are available in the core section of the download page; desktop users generally won't need this, since Clash Plus, Clash Verge Rev, and similar clients already build the same capability into their local UI.

Think twice before exposing the control port beyond your LAN (public internet/port forwarding): even with secret set, this remains a high-privilege management interface and should, in principle, only be reachable from a trusted network — if you must expose it, put it behind a reverse proxy with HTTPS and additional authentication.

API style: RESTfulAuth: Bearer secret
Further Reading — related pages on this site

Once you've read through the handbook, keep going as needed:

  • Quick start guide — the main workflow from install to importing a subscription, for readers who haven't gotten the basics running yet.
  • Download page — clients for every platform plus the mihomo core installer; Clash Plus is the top pick across platforms.
  • FAQ — a Q&A collection organized by basics/setup/usage tips/troubleshooting.
  • How to read runtime logs — the first troubleshooting tool for broken configs or connection issues.
  • How to choose a client — a side-by-side comparison of mainstream clients with picking advice.