Clash Custom Rule Syntax Explained: Match Order, Priority & MATCH Fallback
Clash's routing behavior is entirely determined by the rules list, and most configuration issues aren't caused by using the wrong rule type — they come from misunderstanding match order and priority. This article breaks down the syntax of custom rules line by line, explains how the core matches top-down and stops at the first hit, clarifies how custom rules interact with subscription rules, and shows the correct way to write a MATCH fallback rule along with the most common reasons it fails.
How rules drive traffic routing
Every time the Clash core receives a connection, it extracts the destination address, port, and source characteristics, then checks these against the rules list in the config file, line by line. The first rule that matches determines where the connection goes: a specific proxy node, a proxy group, the built-in DIRECT connection, or a REJECT block. The decision is made the instant a match occurs — every rule after that point is simply skipped.
This makes the rules list the single source of truth for Clash routing. Whether the config comes from a subscription URL, a client's override feature, or manual editing, it all ultimately compiles down to this line-by-line rule list. In practice, using the wrong rule type is rare — most unexpected routing behavior traces back to order: a broader rule sitting earlier in the list intercepts traffic that a more specific rule further down was meant to handle. Understanding match order matters more than memorizing rule types.
Whether traffic enters the core through the system proxy, TUN mode, or a mixed port, it's checked against the same rules list using the same ordering logic.
Anatomy of a single rule
A rule consists of several comma-separated fields. The full form has four fields, though most rule types only use the first three:
TYPE,PAYLOAD,POLICY[,extra-params]
- Type: the matching dimension, such as DOMAIN, DOMAIN-SUFFIX, IP-CIDR, or GEOIP.
- Payload: the specific value to match — a domain, a suffix, an IP range, or a country code.
- Policy: where the matched traffic gets sent. This can be a built-in policy like DIRECT or REJECT, or a node/group name defined under proxies or proxy-groups.
- Extra parameters: currently only no-resolve, used exclusively with IP-based rules — covered in detail further down.
Four hard rules apply when writing custom rules. First, rules live inside the rules block of the config file, with each YAML entry starting on its own dash-prefixed line. Second, policy names must exactly match the names defined elsewhere in the config, including case and spacing — a mismatch will break the entire config on load. Third, domain payloads must be lowercase, with no protocol prefix and no path; anything written with an http:// prefix will never match. Fourth, lines starting with a hash are comments and are ignored by the core.
rules:
- DOMAIN,update.example.com,DIRECT
- DOMAIN-SUFFIX,example.com,Proxy
- DOMAIN-KEYWORD,tracker,REJECT
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
- GEOIP,CN,DIRECT
- MATCH,Proxy
Common rule types at a glance
The table below covers the most common rule types used in custom rules. GEOSITE, PROCESS-NAME, and similar types are mihomo-only — the original Clash Premium core doesn't support them, and mixing them in will cause the config to fail to load.
| Type | Example payload | Matching behavior |
|---|---|---|
| DOMAIN | www.example.com | Matches this exact domain only, not subdomains |
| DOMAIN-SUFFIX | example.com | Matches this domain and all of its subdomains |
| DOMAIN-KEYWORD | tracker | Matches if the domain contains this keyword |
| GEOSITE | cn | Matches a domain category set, mihomo only |
| IP-CIDR | 10.0.0.0/8 | Matches an IPv4 range |
| IP-CIDR6 | fd00::/8 | Matches an IPv6 range |
| GEOIP | CN | Matches by IP geolocation |
| DST-PORT | 443 | Matches by destination port |
| SRC-PORT | 7891 | Matches by source port |
| PROCESS-NAME | curl.exe | Matches by process name, desktop platforms only |
| RULE-SET | adblock | References a rule set hosted under rule-providers |
| MATCH | No payload | Matches all remaining traffic; must go last |
Large lists shouldn't be written into rules line by line. Ad-domain lists or site categories with tens of thousands of entries should be handled through rule-providers instead — the rules list then only needs a single RULE-SET,list-name,policy entry to reference it. The core auto-updates the list on the configured interval, and no matter how many entries it contains, it only occupies one matching slot, which keeps loading and maintenance far lighter.
Match order: top to bottom, first hit wins
When the core processes a connection, it starts at the first line of the rules list and checks each rule in order; the first one that matches takes effect immediately, and checking stops there. This mechanism leads to three direct conclusions:
- Specific rules must come before broad ones. If you want update.example.com to connect directly while the rest of example.com's subdomains go through a proxy, the DOMAIN entry must sit above the DOMAIN-SUFFIX entry — reverse the order and the suffix rule grabs the traffic first, and the exact-match rule never gets a chance to run.
- A rule written later can't override one written earlier. Clash has no last-one-wins cascading logic — whichever rule matches first wins, and the only way to change priority is to reorder the lines.
- Whether custom rules take priority over subscription rules depends purely on their relative position in the final compiled list. Most GUI clients' override feature inserts custom rules ahead of subscription rules so they match first; but if a client appends custom entries after the subscription rules, and the subscription already contains broader entries, the custom rules may never get a turn.
When a rule you wrote doesn't seem to be working, the first move is to open the client's compiled rules list and check exactly where that entry actually sits — not to re-check the syntax over and over.
IP-based rules and the no-resolve parameter
Domain-based rules compare directly against the domain a connection carries, while IP-CIDR and GEOIP need an actual IP to compare against. When the destination is a domain, the core has to run a DNS lookup first before it can proceed with the check. In other words, as long as an IP-based rule exists in the list, every connection that isn't caught by an earlier domain rule may trigger an extra lookup — adding latency and widening DNS exposure.
no-resolve turns this behavior off. An IP rule with no-resolve only matches when the destination is already a literal IP address; the core won't resolve a domain just to satisfy it:
IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
GEOIP,CN,DIRECT,no-resolve
For pure-IP scenarios like private subnets or LAN devices, it's worth attaching no-resolve across the board. On the flip side, GEOIP rules — which rely on resolving first and then matching by location — should never carry this parameter, or domain-based connections will simply slip past them and fall through to the next entry.
The MATCH fallback rule
MATCH is the only rule type with no payload — its syntax is just two fields: MATCH,policy. It catches every connection that hasn't matched anything above it, and three conventions apply: it must be the last line in the rules list; a config only ever needs one MATCH; and it's best written explicitly rather than relying on the core's default behavior.
A MATCH placed in the middle of the list intercepts all remaining traffic, turning every rule below it into dead code — this is the most common, and most easily overlooked, mistake in custom rules. mihomo defaults unmatched traffic to DIRECT, but writing MATCH explicitly makes the final outcome predictable and readable, so you're never left guessing at the core's default behavior later.
Two common approaches: MATCH,Proxy sends everything not explicitly handled through a proxy, paired with direct-connect exceptions above — a blocklist-style setup. MATCH,DIRECT sends only traffic explicitly allowed through a proxy, with everything else going direct — a whitelist-style setup, well suited to cases where only a handful of sites need proxying.
A complete example with match verification
Putting the points above together, a typical custom rules block looks like this:
rules:
# Local and LAN: .lan suffix and private subnets connect directly
- DOMAIN-SUFFIX,lan,DIRECT
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
# Ads and trackers: reject on keyword match
- DOMAIN-KEYWORD,tracker,REJECT
# Exact exception: update server connects directly
- DOMAIN,update.example.com,DIRECT
# Remaining example.com subdomains go through the proxy
- DOMAIN-SUFFIX,example.com,Proxy
# Addresses in mainland China connect directly by geolocation
- GEOIP,CN,DIRECT
# Fallback: everything else goes through the proxy
- MATCH,Proxy
Note that the GEOIP entry in this example doesn't carry no-resolve: any connection made by domain name that isn't caught by an earlier entry will trigger a resolution here before matching by location. To avoid this kind of lookup entirely, use a GEOSITE category list instead to route by domain, and reserve GEOIP for pure-IP connections with no-resolve attached.
You shouldn't have to guess whether a rule is behaving as expected. GUI clients — the connections panel in Clash Verge Rev, or the Connections view in various dashboards — show in real time which rule and policy each connection matched; on the command line, setting the log level to info makes the core print each match as it happens. When the result doesn't match your expectations, use the rule name shown in the panel to go back and check its position in the list — the problem usually becomes obvious right away.
- MATCH isn't placed on the last line, so every rule after it is dead.
- An exact-match rule sits below a broader suffix rule and never gets matched.
- The policy name doesn't match what's defined under proxies or proxy-groups, causing a config load error.
- The domain payload includes a protocol prefix or a path, so matching fails.
- The client appends custom entries after subscription rules, and a broader subscription entry grabs the match first.
- Expecting a later rule to override an earlier one — Clash has no such cascading logic.