Proxy Group Types in Practice
Proxy groups sit right in the middle of the configuration file: rules decide "which type of traffic goes to which group," and proxy groups decide "which node this group uses right now." Whether your traffic splitting works smoothly usually comes down to whether the proxy group structure is well designed. The mihomo core supports five basic types, and their behavior differs as follows.
How the Five Types Behave Differently
| type | Behavior | Typical Use |
|---|---|---|
| select | Manual selection; the chosen item persists until you switch it again manually | Main entry group, region selection group |
| url-test | Periodically tests latency and auto-switches to the lowest-latency node | Everyday traffic that's speed-sensitive |
| fallback | Takes the first node in the list that passes a health check; switches back automatically once an earlier node recovers | Primary/backup structure to keep service uninterrupted |
| load-balance | Spreads connections across multiple nodes by hashing or round-robin | High-concurrency connections, download-heavy traffic |
| relay | Traffic passes through each node in the list in order, forming a chain | Chained proxying, special network path requirements |
select is the most commonly used type and also the most underrated: it makes no automatic decisions at all, and precisely because of that its behavior is fully predictable — well suited as the "human-decided" main entry point. The difference between url-test and fallback is often confused: the former always chases "fastest right now," which can cause frequent flip-flopping when nodes are unstable; the latter chases "the highest-priority node that's still available," only moving when the current node actually fails, prioritizing stability. For interactive applications (remote desktop, voice calls), fallback usually feels better than url-test, because switching means the connection has to be rebuilt.
Three Key Parameters for url-test
An auto speed-test group's behavior is governed by three parameters. interval is the test cycle in seconds; 300 is a common value — set it too short and you'll generate a flood of probe requests, which also drains battery on mobile. tolerance is the margin in milliseconds: a new "fastest" node must beat the current node by at least this much before a switch is triggered; starting at 50 noticeably reduces pointless flip-flopping. When lazy is set to true, the group skips testing while no traffic is passing through it, which can save a meaningful amount of probing overhead for configurations with many nodes.
A Practical Grouping Structure
The recommended structure has three layers: a single select group as the top-level entry, mid-level groups organized by region or purpose, and individual nodes only at the bottom. Rules should always point at the top or mid-level groups, never directly at nodes — that way switching providers or renaming nodes never requires touching a rule. Example:
proxy-groups:
- name: Node Selection
type: select
proxies: [Auto Speed-Test, Fallback, Hong Kong Nodes, Japan Nodes, DIRECT]
- name: Auto Speed-Test
type: url-test
url: https://www.gstatic.com/generate_204
interval: 300
tolerance: 50
lazy: true
proxies: [HK-01, HK-02, JP-01]
- name: Fallback
type: fallback
url: https://www.gstatic.com/generate_204
interval: 300
proxies: [HK-01, JP-01, SG-01]
- name: Hong Kong Nodes
type: url-test
url: https://www.gstatic.com/generate_204
interval: 300
proxies: [HK-01, HK-02]
Pick a lightweight endpoint that returns a 204 status (like generate_204 above) for health checks — the response body is empty and overhead is minimal. Don't use a full webpage for testing; that measures page load time, not link latency.
Rule-Provider Subscriptions
Writing hundreds or thousands of rules directly into the main configuration is the start of a maintenance nightmare: every subscription update means starting over, and adjusting a single rule means hunting through a massive file. Rule providers split rules into independent remote files, leaving only a reference in the main configuration; rule content updates automatically on a schedule, and the main config stays a tidy dozen-or-so lines.
The Three behavior Types
Every rule provider must declare a behavior, which determines how the file content is parsed — the three types cannot be mixed. A domain-type file can only contain domain names (supporting the +. wildcard prefix) and matches fastest; ipcidr can only contain IP ranges (CIDR notation); classical allows full rule syntax (DOMAIN-SUFFIX, IP-CIDR, DST-PORT, and more, mixed together) — flexible, but with slightly higher matching overhead. The principle: use domain or ipcidr whenever you can, and reserve classical for cases that genuinely need mixed conditions.
format and Update Interval
format supports yaml, text, and mihomo's own binary format, mrs. mrs is precompiled — small and fast to load — and is the better choice for large domain/IP sets (like an entire region's IP database); small, hand-maintained rule sets are fine as yaml or text. interval controls the auto-update cycle in seconds; since rule-provider content doesn't change often, 86400 (one day) is a reasonable value — there's no need to be more aggressive.
rule-providers:
streaming:
type: http
behavior: classical
format: yaml
url: https://example.com/rules/streaming.yaml
path: ./rule-sets/streaming.yaml
interval: 86400
cn-ip:
type: http
behavior: ipcidr
format: mrs
url: https://example.com/rules/cn-ip.mrs
path: ./rule-sets/cn-ip.mrs
interval: 86400
rules:
- RULE-SET,streaming,Node Selection
- RULE-SET,cn-ip,DIRECT
- MATCH,Node Selection
The reference syntax is RULE-SET,provider-name,policy. Note that rules are still matched top-to-bottom and stop on the first hit, so the ordering of RULE-SET entries matters just as much as ordinary rules: put high-hit-rate sets first, and place IP-based rule sets after domain-based ones (IP rules trigger DNS resolution, and triggering it earlier than necessary is wasted work). The MATCH catch-all rule always goes on the last line.
A mismatch between the declared behavior and the file's actual content is the most common rule-provider failure — for example, declaring a file written in classical syntax as domain will cause the core to error out or the whole set to silently fail. When referencing a third-party rule provider, confirm the behavior type stated by the publisher before copying it.
DNS Configuration Tuning
DNS is the foundation of traffic-splitting quality. If domain resolution is poisoned, IP-based rules end up making decisions based on the wrong IP; if the resolver is chosen poorly, direct traffic takes a longer path and CDN requests land on slow nodes. The core ships with a complete DNS module, and once you understand what each field is responsible for, most "the rule is clearly written correctly but it's not working" problems resolve themselves.
The Division of Labor Between nameserver and default-nameserver
nameserver is the primary resolver list, responsible for resolving all business domains; using DoH (DNS over HTTPS) addresses is recommended to avoid interference on plain port 53. There's a chicken-and-egg problem here: a DoH address is itself a domain name that needs resolving — that's exactly what default-nameserver is for. It's only responsible for resolving the domains that appear in the nameserver list, and it must be filled with plain-IP traditional DNS servers. The two have distinct roles and can't substitute for each other.
Per-Domain Dispatch with nameserver-policy
nameserver-policy lets you assign a dedicated resolver by domain pattern: domains inside mainland China go through a mainland DNS server to get the nearest CDN, while everything else goes through a trusted encrypted DNS to avoid poisoning. It supports geosite category references, so a single line can cover an entire class of domains — this is currently the recommended split-resolution approach, more intuitive and more predictable than the old fallback + fallback-filter combination.
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
enhanced-mode determines the enhanced resolution mode, taking the value fake-ip or redir-host; how it works together with TUN is covered in the next chapter. listen is the address the core's DNS listens on — under TUN mode, it works with dns-hijack to redirect system DNS queries here, ensuring all resolution passes through the core.
The most direct way to verify DNS is working correctly is to check the resolved IP ownership in the connections panel: mainland sites resolving to mainland CDNs, and no obviously abnormal IPs showing up for other sites, generally means it's configured correctly. If resolution behaves oddly, check first whether default-nameserver is reachable.
TUN and Fake-IP
System proxy mode has a built-in weakness: it only governs "programs that respect the system proxy setting." Command-line tools, games, and some client software simply don't read the system proxy at all. TUN mode creates a virtual network interface on the system and pulls all traffic into the core at the network layer, fixing the incomplete-coverage problem at the root. UWP apps' loopback restrictions, and git or package managers in a terminal, no longer need special handling under TUN mode.
Enabling TUN and Choosing a stack
tun:
enable: true
stack: mixed
auto-route: true
auto-detect-interface: true
dns-hijack:
- any:53
auto-route automatically takes over system routing, and auto-detect-interface automatically identifies the physical outbound interface — both are usually left on. dns-hijack redirects queries sent to any port 53 to the core's DNS, a key piece of how TUN and Fake-IP work together. stack determines the virtual interface's protocol stack implementation: system uses the OS network stack directly and offers good throughput; gvisor is a userspace implementation with good compatibility; mixed splits the difference, sending TCP through system and UDP through gvisor — a reasonable starting point on desktop. Prerequisites for enabling TUN differ by platform:
| Platform | Prerequisite | Notes |
|---|---|---|
| Windows | Run as administrator, or install the client's system service | Clash Verge Rev offers a service mode to avoid elevating every time |
| macOS | First activation requires entering an administrator password to authorize | Once authorized, it works silently afterward |
| Android | Uses the system VpnService interface; just authorize the VPN connection | No root needed; see the tip below for background policy |
| Linux | Root privileges, or grant the binary the CAP_NET_ADMIN capability | For server use, running as a persistent systemd service is recommended |
How Fake-IP Works, and fake-ip-filter
Fake-IP is a resolution strategy paired with TUN: when a program issues a domain query, the core doesn't wait for real resolution to finish — it immediately returns a "fake IP" from a reserved range (198.18.0.1/16 by default) and remembers the mapping. When a connection arrives, the core looks up the domain from the fake IP and matches it directly against domain-based rules — skipping a serial DNS wait, speeding up connection setup, and making rule matching more accurate. The tradeoff is that some programs break when they get a fake IP: LAN discovery, time sync, and connectivity checks — anything that takes the IP at face value — need to be exempted, which is what fake-ip-filter is for:
dns:
fake-ip-filter:
- "*.lan"
- "+.local"
- "time.windows.com"
- "+.msftconnecttest.com"
- "+.stun.*.*"
Domains that match the filter list get real resolution and a real IP. Keep the list minimal — the more entries you add, the more Fake-IP's benefit gets diluted. Also note that switching between Fake-IP and redir-host can leave stale resolution caches in the system and browser, which shows up as odd behavior on some sites for a while; restarting the client or flushing the system DNS cache fixes it.
Keeping TUN on long-term has a noticeable impact on mobile battery life — the tradeoffs between background keep-alive and manufacturer power-saving policies are covered in the blog post "Diagnosing Abnormal Battery Drain from Clash on Android"; issues like crash-on-launch or TUN failing to enable on desktop are covered item by item in the troubleshooting section of Common Questions.
Domain Sniffing
Domain-based rules are the easiest to write, but two kinds of traffic arrive at the core with only an IP and no domain: first, programs that resolve DNS themselves and connect directly to the IP (bypassing the core's resolution step entirely); second, cases outside the Fake-IP mapping. Without a domain, DOMAIN-SUFFIX and similar rules simply can't match, and the traffic falls through to IP-based rules or the catch-all. Domain sniffing (sniffer) works by "reading" the domain back out of the traffic itself: the TLS handshake's SNI field, the HTTP request's Host header, and QUIC's handshake packets all carry the target domain. The core parses these protocol features early in connection setup and feeds the recovered domain back into the rule engine.
Configuration Structure
sniffer:
enable: true
sniff:
HTTP:
ports: [80, 8080-8880]
override-destination: true
TLS:
ports: [443, 8443]
QUIC:
ports: [443]
force-domain:
- "+.example-cdn.net"
skip-domain:
- "+.push.apple.com"
Under sniff, port ranges to sniff are declared per protocol — only declared ports get sniffed, and the narrower the range, the lower the overhead. override-destination determines whether the sniffed domain overrides the connection's destination address — once enabled, the connection continues to be handled as a domain, with both rule matching and DNS dispatch going by domain. force-domain forces sniffing overrides for matched domains, commonly used for apps that connect directly to an IP even after receiving a Fake-IP; skip-domain is the whitelist — matched domains skip the override, and it's worth listing long-lived connections that are sensitive to their destination, like push services, to avoid sniffing interference.
Sniffing isn't decryption: it only reads the plaintext metadata carried during the protocol handshake (SNI, Host) and never touches the encrypted payload. For most configurations, enabling HTTP and TLS sniffing already covers the main cases; add QUIC only if you need it.
Local Overrides and Multi-Subscription Merging
Editing the configuration file delivered by a subscription directly is the most common maintenance trap for newcomers: the next subscription update overwrites the whole thing with the provider's template, and every manual edit vanishes instantly. A good chunk of the cases in the blog post "Troubleshooting a Failed Clash Subscription Update" trace back to exactly this. The right approach is to separate "what the provider gives you" from "what you write yourself": treat the subscription purely as a node source, and put personalized content into an override layer or a separate provider.
Client Overrides: Merge and Script
Clash Verge Rev offers two tiers of overrides, both applied after the subscription updates and before the core loads the config, so they're never wiped out by a subscription. Merge overrides are declarative: you write a YAML snippet, and its fields merge into the final configuration — suited to structural additions like extra DNS settings, rule providers, or a few custom rules pinned to the top. Script overrides are a JavaScript function that receives the full configuration object and returns a modified one — suited to cases that need logic, like filtering nodes by name with a regex, bulk-adding nodes to proxy groups, or dynamically generating groups based on node count. The two can be combined; Merge is recommended by default, and reach for Script only when you genuinely need programmatic control. Clash Plus also offers a way to keep subscriptions and local configuration separate; see Client Comparison for how the clients differ on this front.
proxy-providers for Multi-Subscription Merging
If you hold subscriptions from multiple providers, there's no need to switch configurations back and forth. proxy-providers declares each subscription as its own node source, updated independently, and the use field on a proxy group pulls nodes from multiple sources into a single group:
proxy-providers:
airport-a:
type: http
url: https://a.example.com/sub?token=xxxx
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://b.example.com/sub?token=xxxx
path: ./providers/airport-b.yaml
interval: 43200
proxy-groups:
- name: Node Selection
type: select
use: [airport-a, airport-b]
- name: Auto Speed-Test
type: url-test
url: https://www.gstatic.com/generate_204
interval: 300
use: [airport-a, airport-b]
use and proxies can coexist in the same group — the former pulls in every node from the listed providers, and the latter appends hand-written nodes or other groups. health-check gives a provider its own built-in testing, and url-test groups referencing it can reuse those results directly. interval is the subscription pull cycle in seconds; 43200 (half a day) is plenty for most providers, and a failed pull from one provider doesn't affect the others — one of the natural resilience benefits of a multi-subscription structure.
A provider's filter field (a regex) lets you admit only specific nodes into a group — filtering by region name, for instance — and combined with use, you can build a structure that aggregates same-region nodes from multiple providers for speed testing, entirely transparent to the rule layer.
External Controller Panel
At runtime, the core exposes a RESTful control API — node switching, latency testing, connection inspection, and config reloading all go through it, and desktop client UIs are essentially wrappers around this same API. Using the external controller directly matters in two scenarios: running a bare core on a router or server with no GUI, and wanting to manage things through a browser panel or automation script.
Enabling the Interface and Authentication
external-controller: 127.0.0.1:9097
secret: "your-strong-secret"
external-ui: ./ui
external-controller declares the listen address and port. Bind it to 127.0.0.1 for local use only; bind it to 0.0.0.0 only when another device on the LAN needs access (like managing a router's core from your phone), and in that case secret must be set to a sufficiently strong random string — every request must carry it in the Authorization header. external-ui points to a directory of static web-panel files that the core will host directly; open the control address in a browser and it just works. Popular open-source web panels (the metacubexd and yacd families) can simply be unzipped into that directory.
Automating with the API
The interface is standard HTTP + JSON, and curl alone can drive it. Viewing every proxy group and node status:
Switching the selected node in a select group by sending a PUT request to the group name:
Other commonly used endpoints include /connections (a live connection list — the first place to check "which policy did this traffic actually take"), /logs (a log stream), and /configs (changing the port or mode at runtime). Wire these endpoints into a script and you can build unattended logic like scheduled speed tests or automatic failover.
The control interface has full control over the core. Binding a non-loopback address without setting a secret is equivalent to opening up proxy control to your entire LAN; exposing it to the public internet is never acceptable under any circumstances. The secret in the example is a placeholder — replace it with your own randomly generated value before actual use.
Reading Path and Further Reading
The seven chapters have a clear dependency structure: proxy groups and rule providers form the skeleton, DNS is the foundation, TUN, Fake-IP, and sniffing solve "how completely traffic is captured and how accurately it's matched," overrides and merging solve long-term maintenance, and the external controller handles runtime observation and automation. Jump into whichever chapter addresses your actual pain point — there's no need to read start to finish. If you run into an unfamiliar term while configuring, the glossary defines terms across five categories: core & client, proxy protocols, rules & proxy groups, DNS & traffic processing, and configuration fields; if you hit an error or odd behavior after changing your configuration, check the troubleshooting section of Common Questions first; if you still haven't decided which client to use for these configurations, Client Comparison offers recommendations by platform and usage habits, and the full first-install checklist is in the blog post "First-Run Setup Checklist for the Clash Client". This page will keep being revised as core configuration fields evolve, so it's worth bookmarking for reference.