CH-01Proxy Groups: The Gear-Shifting Mechanism of Your Config
Start by separating two jobs. Rules decide who handles a given piece of traffic. Proxy groups decide which node actually carries it once a rule has claimed it. In a config built to last, almost every rule points to a proxy group rather than a single node—nodes come and go, but a group's name stays put. Set the groups up properly, and swapping providers or nodes later is just swapping magazines, not rebuilding the gun.
Five Types, One Spec Table
| Type | How it picks a node | Typical use |
|---|---|---|
select | Manual pick, stays fixed until changed | Master switch, region toggles, anything needing a human decision |
url-test | Tests latency on a schedule, auto-picks the fastest | Everyday auto mode, the default destination for most traffic |
fallback | Tries the list in order, moves down only when the current one fails | Scenarios with a clear primary/backup priority |
load-balance | Spreads connections across multiple nodes | Spreads load when a single node is rate-limited; some services are sensitive to IP changes |
relay | Chains nodes in order, forwarding traffic hop by hop | Chained proxying—high overhead and latency, use only for special needs |
The picking rule is simple: use url-test when you're happy to let the machine decide, use select when a human should call the shot, and treat the other three as special-purpose parts for specific scenarios. Get comfortable with just the first two and you've already covered ninety percent of what you'll need.
url-test's Three Knobs
url is the test target—by convention a lightweight endpoint that returns a 204 status, such as https://www.gstatic.com/generate_204. It sends back an empty response, so what you're measuring is pure link latency, not page-load time. interval is the test cycle in seconds; 300 means one round every five minutes. Set it too low and you flood the network with test requests; set it too high and a dead node can sit undetected for ages. tolerance is the allowed difference in milliseconds: if the gap between the current fastest node and a new candidate stays under this value, the group won't switch. Skip it, and two nodes bouncing between 80ms and 85ms will make the group flap back and forth, breaking long-lived connections along the way. One more knob worth knowing: lazy: true skips testing altogether while the group is idle, saving a batch of pointless background requests.
A Layering Structure That Holds Up
In practice, three layers work well: a select master switch at the top, auto-test and per-region groups in the middle, and individual nodes at the bottom. Rules always point to a group name, never to a specific node. To lock onto a region manually, switch it in the master switch; to go fully automatic, switch back to the auto-test group. Here's the layout:
proxy-groups:
- name: Manual-Switch
type: select
proxies: [Auto-Speed, HK-Nodes, JP-Nodes, DIRECT]
- name: Auto-Speed
type: url-test
url: https://www.gstatic.com/generate_204
interval: 300
tolerance: 50
lazy: true
proxies: [HK-01, HK-02, JP-01, JP-02]
- name: HK-Nodes
type: url-test
url: https://www.gstatic.com/generate_204
interval: 300
proxies: [HK-01, HK-02]
- name: JP-Nodes
type: select
proxies: [JP-01, JP-02]
CH-02Rule Providers: Turning Rules into Swappable Magazines
Writing hundreds of rules directly into your main config is like welding every part to the chassis—swapping out even one means tearing down the whole machine. rule-providers takes a different approach: rules live in pluggable magazines. Your main config keeps just a one-line RULE-SET reference, while the actual magazine sits at an external URL and updates itself on schedule. The maintainer edits the magazine; you don't touch anything.
Field Reference
Each provider has five key fields. type is either http (fetched from a URL) or file (read from a local file); behavior declares what kind of ammunition the magazine holds—more on this below; format is the file format, either yaml or text; path is the local cache path after download; interval is the auto-update cycle in seconds, with 86400 meaning once a day. The cache matters because if you restart offline, the core reads the local copy directly instead of refusing to start just because it couldn't fetch a rule set.
Choosing Among the Three behavior Options
A domain magazine holds only domain names, and the core builds a dedicated index for it—fastest matching, lowest memory use. ipcidr holds only IP ranges and gets the same dedicated treatment. classical can hold anything (DOMAIN-SUFFIX, IP-CIDR, PROCESS-NAME all mixed together), which is flexible but matches rule by rule, so performance drops once the list grows. The rule of thumb: use domain for a pure domain list, ipcidr for a pure IP list, and reserve classical for genuinely mixed content. Loading a pure domain list into classical works, but it's like using a delivery truck to send a single envelope.
A Working Example and Update Cadence
rule-providers:
streaming:
type: http
behavior: classical
format: yaml
url: "https://example.com/rulesets/streaming.yaml"
path: ./rulesets/streaming.yaml
interval: 86400
cn-ip:
type: http
behavior: ipcidr
format: text
url: "https://example.com/rulesets/cn-ip.txt"
path: ./rulesets/cn-ip.txt
interval: 86400
rules:
- RULE-SET,streaming,Manual-Switch
- RULE-SET,cn-ip,DIRECT,no-resolve
- MATCH,Manual-Switch
no-resolve means matching only happens when the traffic already carries an IP—no proactive lookup. That saves a round of waiting and keeps the lookup from going through a channel it shouldn't. Place IP rules after domain rules, and attach this flag to as many of them as you can.Set the update interval based on the magazine's nature: a once-a-day refresh is plenty for community-maintained routing lists, while lists you maintain and tweak often can drop to a few hours. Troubleshooting a failed rule-provider fetch follows the same logic as a failed subscription update—see the troubleshooting category in the Help Center.
CH-03DNS Tuning: Half of Your Problems Live in Resolution
"The proxy is on but the page won't load," "the rule is right there but never matches"—half the time, DNS is the culprit. The core ships with its own DNS resolver: get it configured well and resolution is fast and stable; get it wrong and you'll cycle through poisoning and leaks. This chapter walks through every slot in the resolution chain.
Understand the Resolution Chain First
The resolver has three layers. default-nameserver is the bootstrap layer: when your primary resolver is itself a domain name (like the DoH address https://doh.pub/dns-query), something has to resolve that domain to an IP first, and that's the bootstrap layer's job—so it can only hold plain IPs; putting a domain here creates a chicken-and-egg loop. nameserver is the primary layer that handles everyday lookups. fallback is the backup layer, there specifically for cases where the primary layer might be poisoned. Each layer has its own job—don't stuff a DoH address into the bootstrap layer, and don't leave the primary layer empty.
How nameserver and fallback Divide the Work
The two work concurrently: every query goes out to the primary and backup at the same time, and fallback-filter decides whose answer to trust. The most common decider is geoip: true combined with geoip-code: CN—if the primary layer's answer lands in mainland China's IP ranges, it's trusted as a legitimate domestic result; if it lands outside those ranges, the backup's answer is used instead (a domestic resolver returning a non-mainland IP looks suspiciously like poisoning). The cost is one extra query per lookup, and what you get in return is resistance to poisoning. If your primary layer already runs over an encrypted channel (DoH/DoT), the poisoning risk is already low, so fallback can be kept simple.
nameserver-policy: Assigning a Dedicated Resolver per Domain
For some domains you know exactly where they should be resolved: services within mainland China should use a mainland resolver to get the nearest CDN node, while speed-test and LAN domains each have their own destination. nameserver-policy is that routing table, and it supports wildcards and geosite categories. Full example:
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
fallback:
- https://1.1.1.1/dns-query
- https://8.8.8.8/dns-query
fallback-filter:
geoip: true
geoip-code: CN
nameserver-policy:
"geosite:cn": https://doh.pub/dns-query
"+.lan": 223.5.5.5
dns section, restart the core and reconnect your network before testing—both the system and individual apps keep resolution caches, and without a clean slate the new config can look like it isn't working. The difference between the two enhanced-mode values is covered in the next chapter.CH-04TUN & Fake-IP: System-Level Fallback and the Ticket-Number Trick
TUN: Taking Over Traffic That Won't Play Along
System proxy settings work on the honor system: only apps that bother to check them actually use them. Command-line tools, some game clients, and certain desktop software ignore the setting entirely and send traffic straight out. TUN mode takes a different tack—it inserts a virtual network adapter and intercepts every bit of the device's outbound traffic at the network layer, handing it to the core. It doesn't matter whether an app cooperates; anything trying to sneak past still goes through security.
Worth clarifying for Android: the VPN channel the client requests from the system when you tap "Start" is, at its core, a TUN adapter. So Android users are already in TUN mode by default and don't need to configure any of this—it's desktop platforms that require explicitly turning TUN on. Client downloads for each platform are on the download page, with a side-by-side comparison in the comparison guide. Key fields on desktop: stack is the protocol stack implementation—system performs well but depends on OS support, gvisor is a user-space implementation with steady compatibility, and mixed takes the best of both; when unsure, pick mixed. auto-route takes over the routing table automatically. dns-hijack intercepts plaintext DNS requests headed to any port 53 and redirects them back to the core, preventing an app's built-in resolver from bypassing your routing rules.
Fake-IP: Hand Out a Ticket Number First, Handle Business Later
Think of it like a bank's ticket-number system. When an app asks "what's the IP for this domain," the core skips real resolution and instantly hands out a fake address from the reserved range 198.18.0.1/16—zero latency. The app connects using that fake address; once the connection reaches the core, the core looks up the ticket number, recovers the real domain, and routes by domain rule. If the traffic needs to go through a proxy, the actual resolution happens at the exit side instead. Two benefits follow: connections start faster because there's no local resolution wait, and domain-rule hit rates stay high because the core always knows which domain sits behind each connection.
The catch is that a ticket number is useless outside the branch. LAN device discovery, printers, some online games, and anything that needs to report a real IP back to a server all fall apart once they're handed a fake address. The fix is fake-ip-filter: domains on the list skip the ticket number entirely and get a genuine resolution instead.
tun:
enable: true
stack: mixed
auto-route: true
auto-detect-interface: true
dns-hijack:
- any:53
dns:
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
fake-ip-filter:
- "*.lan"
- "+.local"
- "+.msftconnecttest.com"
- "+.stun.*.*"
fake-ip vs. redir-host
| Aspect | fake-ip | redir-host |
|---|---|---|
| Local resolution wait | None—returns a fake address immediately | Yes—waits for real resolution to finish |
| Domain-rule hit rate | Stable—the core always holds the domain mapping | Depends on the resolution result; blind in some cases |
| LAN compatibility | Needs fake-ip-filter to allow through | Naturally compatible |
| Best suited for | Most everyday use | Heavy LAN service use, special compatibility needs |
enhanced-mode values, always disconnect and reconnect once: an app may still be holding an address issued under the old mode, and mixing old and new ticket numbers can cause a stretch of bizarre connection failures.CH-05Domain Sniffing: A Barcode Reader at the Inbound Gate
Fake-IP patches one gap, but there's still a crack in the chain: some traffic arrives at the core carrying only an IP, no domain. The usual culprits are apps with their own encrypted resolution (built-in DoH that bypasses the core's DNS) or clients that connect directly to hardcoded IPs. Domain rules go blind for this traffic entirely, leaving only IP rules or MATCH as a fallback—and routing accuracy drops sharply.
sniffer acts as a barcode reader at the inbound gate: as traffic comes in, it reads the domain back out of the TLS handshake's SNI field or the HTTP request's Host header, tags the connection with it, and only then sends it on for rule matching. Once the domain is recovered, the rules work as intended.
Config Fields
Under sniff, you declare which ports to scan per protocol: TLS typically covers 443 and 8443, while HTTP covers 80 and common proxy port ranges. override-destination decides whether a sniffed domain replaces the original target address—recommended for HTTP. force-domain is a mandatory-sniff list: domains on it get re-sniffed even if a resolution already exists; skip-domain is an exemption list—for services with strict certificate checks or that are sensitive to SNI changes (Apple push notifications are a regular offender), sniffing can actually break the connection, so listing them lets traffic through untouched.
sniffer:
enable: true
sniff:
TLS:
ports: [443, 8443]
HTTP:
ports: [80, 8080-8880]
override-destination: true
force-domain:
- "+.v2ex.com"
skip-domain:
- "+.push.apple.com"
0-65535. If an app starts dropping connections frequently once sniffing is on, the first move is adding its domain to skip-domain.CH-06Local Overrides & Multi-Subscription Merging: Changes That Survive Updates
Why Not Just Edit the Subscription File Directly
Editing rules or adding nodes straight into the file your subscription delivers feels convenient in the moment, but it ends badly: the next subscription update overwrites the entire file with the server's new version, wiping out every change you made. The right approach is overrides—treat the subscription's original file as read-only forever, keep your changes in a separate local layer, and have them automatically replayed onto the new file after every update. Your changes and the subscription stop interfering with each other.
Where to Find Overrides in Your Client
Mainstream clients all build in this mechanism—the name varies, but the idea is the same. Clash Verge Rev, for example, offers two tiers: Merge combines declarative snippets, good for structured changes like appending rules or proxy groups; Script runs actual code to rewrite the whole config programmatically, suited to more involved work like bulk-renaming nodes or conditional filtering. Don't reach for Script if Merge can do the job—a declarative snippet is readable at a glance, while a broken script is far harder to debug. The comparison guide has a dedicated section on how override support differs across clients.
Merging Multiple Subscriptions with proxy-providers
If you're running subscriptions from more than one provider, don't bother switching config files back and forth. proxy-providers works on the same idea as the rule-provider concept: each subscription becomes a node magazine, and a proxy group's use field can mount several magazines at once, blending nodes from every provider into a single auto-test group where the fastest one wins.
proxy-providers:
airport-a:
type: http
url: "https://example.com/sub/a.yaml"
path: ./providers/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/b.yaml"
path: ./providers/b.yaml
interval: 43200
health-check:
enable: true
url: https://www.gstatic.com/generate_204
interval: 600
proxy-groups:
- name: All-Nodes
type: url-test
use: [airport-a, airport-b]
url: https://www.gstatic.com/generate_204
interval: 300
health-check periodically checks the health of nodes inside a magazine, and the auto-test group skips any that go down; interval: 43200 means the subscription itself is re-fetched every twelve hours. For recognizing subscription link formats and where to import them, see this article first: Clash Subscription Link Import Formats.
url field before posting screenshots or asking for help, and never upload a config file with a live subscription address to a public repository; that's the same as leaving your keys hanging on the door.CH-07External Controller: Giving the Core a Remote Interface
Three Fields to Enable Remote Control
external-controller declares the listen address for the RESTful API, conventionally 127.0.0.1:9090; secret is the access password that every request must include; external-ui points to a local directory holding the web dashboard's static files, so pointing a browser at the listen address opens the graphical interface directly. Every dashboard out there (metacubexd, yacd, and their relatives) is essentially a different skin on the same API underneath.
external-controller: 127.0.0.1:9090
secret: "your-strong-password"
external-ui: ./ui
No Dashboard Needed: Three Handy Commands
The API is a usable tool on its own—call it directly from scripts or automation:
# List all proxy groups and node status
curl -H "Authorization: Bearer your-strong-password" \
http://127.0.0.1:9090/proxies
# Switch the "Manual-Switch" group to HK-01
curl -X PUT -H "Authorization: Bearer your-strong-password" \
-d '{"name":"HK-01"}' \
http://127.0.0.1:9090/proxies/Manual-Switch
# Test latency for a single node
curl -H "Authorization: Bearer your-strong-password" \
"http://127.0.0.1:9090/proxies/HK-01/delay?timeout=5000&url=https://www.gstatic.com/generate_204"
Three Panels Worth Using Regularly
Connections: watch in real time which rule and which exit each connection is using—questions like "why did this traffic go direct" get answered at a glance here. Logs: bump the level to debug temporarily to print the full rule-matching process line by line; remember to switch it back afterward, since debug logging is very verbose. Provider management: manually trigger an immediate update for a subscription or rule provider instead of waiting for interval to elapse.
external-controller to 0.0.0.0 without setting a secret is like leaving the remote hanging at the front door of the public internet: anyone who can reach that port can change your exit node and watch your connections. Lock it to 127.0.0.1 for local-only use; if you genuinely need LAN access, a strong password is mandatory.CH-08Config Troubleshooting Quick Reference
Match your symptom, check the most common cause first, then follow the link to the relevant chapter or article. Most issues get resolved within the first two steps.
| Symptom | Most likely cause | Check here first |
|---|---|---|
| Changed a rule but nothing happened | Edited the raw subscription file; an update wiped it out | CH-06 Overrides |
| Subscription update fails | Link expired, or the update request itself never went through | Help Center troubleshooting category |
| All node latencies time out | Test URL unreachable, or system clock drift is too large | First Connection & Latency Testing |
| Some apps bypass the proxy | The app ignores system proxy settings | CH-04 TUN; for Windows Store apps, see UWP Loopback Restriction Fix |
| Latency looks fine but pages won't load | A DNS config issue, or stale cache after switching modes | CH-03 DNS and CH-04 Fake-IP |
| LAN devices or printers become unreachable | A local domain got issued a Fake-IP ticket number | fake-ip-filter in CH-04 |
| An app drops connections frequently once sniffing is on | The service is sensitive to SNI changes | skip-domain in CH-05 |
For anything not covered in the table, browse the Help Center under its four categories—Basics / Setup / Usage Tips / Troubleshooting. If you suspect the client itself is the problem, first check its capability boundaries in the comparison guide, and if needed, switch to the top-recommended Clash Plus from the download page. If you've forgotten a basic step, head back to Getting Started for a refresher.