Basic rule structure and placement
Routing in Clash and the mihomo core is entirely built on the rules field of the config file. Each rule is a single line following a three-part format: "match type, match value, target policy", separated by commas. Here's a typical example:
rules:
- DOMAIN-SUFFIX,google.com,PROXY
- DOMAIN-SUFFIX,cn,DIRECT
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
- GEOIP,CN,DIRECT
- MATCH,PROXY
The first field is the match type, which determines what dimension is used to evaluate traffic. The second field is the actual value being matched, such as a domain suffix or IP range. The third field is the policy or policy group that handles the connection once matched — common values include DIRECT (bypass the proxy), PROXY (route through a policy group), and REJECT (block). Some match types support an optional fourth parameter — for example, no-resolve after IP-CIDR skips DNS resolution and matches directly against the destination IP, which is useful for avoiding unnecessary lookup requests.
The rules field sits near the end of the config file, usually right after proxy-groups. Custom rules and rules pulled from a subscription get merged into a single list, and the resulting order directly determines routing outcomes — more on that in Section 3.
Common match types explained
Domain-based matching
DOMAIN matches a full domain exactly, applying only to that exact string. DOMAIN,api.example.com,PROXY blocks only that specific subdomain and has no effect on www.example.com.
DOMAIN-SUFFIX matches a domain suffix and is the most commonly used type. DOMAIN-SUFFIX,google.com,PROXY matches google.com, www.google.com, mail.google.com, and every other domain ending in that suffix — ideal for routing an entire site under one policy.
DOMAIN-KEYWORD matches a keyword anywhere in the domain string, regardless of position, written as DOMAIN-KEYWORD,youtube,PROXY. This match is broad and can accidentally catch unrelated domains, so it's best used only when the keyword is distinctive enough.
DOMAIN-REGEX allows regular expression matching for domains, useful for patterns that suffix or keyword matching can't cleanly cover. It's more costly to write and debug, so it shows up far less often in typical configs.
IP and subnet matching
IP-CIDR matches an IPv4 subnet, written as IP-CIDR,base-address/prefix-length,policy, and is commonly used to allow local network ranges directly, e.g. IP-CIDR,10.0.0.0/8,DIRECT,no-resolve. IP-CIDR6 is the IPv6 equivalent with the same structure.
GEOIP matches based on the country or region a destination IP belongs to, using a built-in IP geolocation database. Written as GEOIP,CN,DIRECT, it means any connection whose destination IP is in mainland China goes direct — no need to enumerate subnets manually, which is far less maintenance than a hand-written IP-CIDR list.
Geosite and rule-set matching
GEOSITE is mihomo's compatibility layer for the v2ray rule-set ecosystem, matching domains by category. A single rule like GEOSITE,cn,DIRECT can cover thousands of commonly used domains in mainland China, making it one of the lowest-maintenance ways to route domain traffic. Before using it, make sure the corresponding rule-providers entry is declared in the config, otherwise the core will fail to start with a "rule-set not found" error.
RULE-SET references a custom or subscription-provided rule-set file, written as RULE-SET,set-name,policy. The rule set itself is a separate file containing large numbers of entries like DOMAIN-SUFFIX or IP-CIDR, with its source URL and refresh interval declared via rule-providers. The benefit is that rule content can be updated independently of the main config, without editing the rule list every time.
Process and port matching
PROCESS-NAME matches based on the local process name that initiated the connection, useful for assigning a specific client its own exit path — for example, routing a particular download tool direct. DST-PORT matches by destination port, often used to carve out mail or gaming ports for separate handling. Both rely on platform-level process detection and may not work reliably on some mobile or sandboxed environments, so it's worth verifying on desktop first before relying on them elsewhere.
Fallback matching
MATCH is a wildcard rule with no match value, and it must be the last line in the rule list — it defines the default destination for any connection that didn't match anything above it. Most configs point MATCH at a policy group rather than a single hard-coded node, so the exit can be switched anytime from the client UI without editing the config file.
Match type casing isn't strictly enforced, but community convention uses all caps. Sticking to this makes configs easier to read, share, and search later.
Priority logic: top-down, first match wins
Clash and the mihomo core process the rule list under a strict sequential-match principle: starting from the first line of rules, each connection is checked line by line, and as soon as one line matches, that line's policy is applied immediately and evaluation stops. Even if a more precise or more suitable rule appears further down, it will never be checked. This means rule order itself is the priority — not which match type is "more specific."
Here's a common mistake:
rules:
- DOMAIN-KEYWORD,google,DIRECT
- DOMAIN-SUFFIX,google.com,PROXY
- MATCH,PROXY
In this config, the first line's keyword match catches every domain containing the string google and routes it direct, so the second line's intended proxy routing for DOMAIN-SUFFIX,google.com,PROXY never gets a chance to run — the connection has already been intercepted and handled by the first line before it ever reaches the second. Getting the order backwards produces the exact opposite of what's intended, and client UIs generally won't flag this kind of logical conflict — it's up to the user to catch it.
Given this mechanism, the recommended ordering principle is specific to broad: put exact matches like DOMAIN and DOMAIN-SUFFIX first, then broader matches like DOMAIN-KEYWORD, GEOSITE, and GEOIP, and finish with MATCH as the catch-all. A broad rule placed too early will "intercept" traffic that was meant to reach a more precise rule further down.
Should custom rules go before or after subscription rules?
Most users don't write their config from scratch — it's a complete file generated by a subscription provider, already containing its own rule list. Where a new custom rule gets inserted determines whether it actually takes effect.
The rule is: custom rules should be inserted at the very top of the subscription's rule list, not appended at the end. This follows directly from the "first match wins" mechanism covered above — a subscription's rule list usually already covers a huge number of common domains and IP ranges, so if a custom rule is added at the end, the connection will most likely already have been matched by a subscription rule first, and the custom rule never gets a turn. Only by placing custom rules at the very top can they actually carry higher effective priority than subscription rules.
A common practice is to mark off a separate commented block at the top of the rule list, making it easy to locate and preserve during the next subscription update:
rules:
# ↓↓↓ Custom rules — keep these when refreshing the subscription ↓↓↓
- DOMAIN-SUFFIX,internal.mycompany.com,DIRECT
- DOMAIN-KEYWORD,ads,REJECT
# ↑↑↑ End of custom rules ↑↑↑
- DOMAIN-SUFFIX,google.com,PROXY
- GEOSITE,cn,DIRECT
- MATCH,PROXY
One important caveat: if the client is set to auto-update the subscription, and the client itself doesn't support advanced features like "keep custom rules separate and auto-prepend on merge," every subscription refresh will overwrite the local config entirely with the remote file, wiping out any rules manually added at the top. When using a client that supports "config merging" or "override" features — such as Clash Verge Rev or FlClash — it's better to write custom rules in a dedicated override file rather than editing the subscription-generated main config directly, so the custom section survives future subscription updates.
Editing a subscription-generated config file directly counts as a temporary change — most clients will overwrite the entire file the next time the subscription is pulled. To keep custom rules long-term, use the client's override, merge, or local-rule features instead of manually re-adding them after every update.
Typical scenarios and rule combinations
The combinations below cover the needs that come up most often in everyday configs — feel free to adjust the policy group names and use them directly.
Direct for LAN and mainland China, proxy for everything else
rules:
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
- IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
- IP-CIDR,127.0.0.0/8,DIRECT,no-resolve
- GEOSITE,cn,DIRECT
- GEOIP,CN,DIRECT
- MATCH,PROXY
This set first excludes local network and loopback addresses, so LAN traffic doesn't get mistakenly routed through the proxy chain. GEOSITE then covers mainland China domains, GEOIP catches mainland IPs whose domains aren't in the geosite category, and MATCH sends everything else to the proxy policy group.
Blocking ads and trackers
rules:
- DOMAIN-KEYWORD,doubleclick,REJECT
- DOMAIN-SUFFIX,adservice.google.com,REJECT
REJECT drops the matched connection outright, and the client sees that resource fail to load or time out. These rules must also come before any broader rule that would otherwise allow the domain through — otherwise the more permissive rule handles it first and the block never applies.
Forcing specific apps direct
rules:
- PROCESS-NAME,WeChat.exe,DIRECT
- PROCESS-NAME,com.tencent.mm,DIRECT
Some messaging or local-network apps are sensitive to latency and connection stability, and often perform better routed direct rather than forced through a proxy. Desktop process names typically include the file extension, while Android apps should use the package name.
Troubleshooting rules that don't seem to work
If routing results look wrong after writing a rule, work through the checklist below first rather than assuming the node itself is broken:
Check rule order
Look for a broader rule placed above the target rule that's intercepting the traffic first. This is by far the most common cause.
Verify rule-providers loaded correctly
When using
GEOSITEorRULE-SET, if the rule-set URL is unreachable or the format doesn't match, the core may fail to start or silently skip the rule — check the client logs to confirm it loaded.Double-check policy group name spelling
The policy name in the third field must exactly match a name defined in
proxy-groups, including case and spacing — a typo will make the rule fail silently or throw an error.Check for an earlier catch-all rule
If the config has multiple
MATCHlines or an overly broadGEOIP/GEOSITErule placed too early, a newly added precise rule may end up stuck behind it and never actually run.
Once you've internalized the "top-down, first match wins" logic behind the rule engine, both inheriting an unfamiliar subscription config and building a routing setup from scratch become much easier to reason about. It's worth keeping custom rules grouped together with clear comment markers — it pays off a lot over time.