Clash Rule Priority Explained: Writing Custom DOMAIN and IP-CIDR Rules

A breakdown of the top-down rule matching logic, with example syntax and ordering guidance for common rule types like DOMAIN-SUFFIX, IP-CIDR, and GEOIP to prevent rules from overriding each other.

The core principle: top-down, first match wins

When Clash's routing engine processes a connection request, it checks each entry in the rules list in order. As soon as one rule's condition matches the request, the policy group or proxy node specified by that rule is applied immediately, and no further rules are evaluated. This means the order of the rule list itself determines priority, regardless of rule type — a DOMAIN-KEYWORD written earlier can override a GEOIP rule written later, and vice versa.

This mechanism has two direct consequences. First, the earlier a rule entry appears, the narrower and more specific its scope should be — for example, a direct-connection or block rule for a particular domain. The later it appears, the broader and more general it should be — for example, a country/region-based GEOIP rule or the final catch-all MATCH. Second, when adding a custom rule, its position determines whether it gets pre-empted by an existing rule above it. Many "the rule is written but doesn't seem to work" complaints trace back to ordering issues, not syntax errors.

NOTE

Clash Meta (mihomo) follows the same rule-engine logic as Clash — top-down, first match wins. The main difference is a richer set of supported rule types (such as PROCESS-NAME, RULE-SET, and logical rules AND/OR/NOT), but the ordering principle itself is unaffected.

Common rule types and syntax

The general format for a rule entry is TYPE,MATCH-CONTENT,POLICY, where the policy can be a specific node name or a policy group name (such as PROXY, DIRECT, or REJECT). Below are the types you'll use most often.

The DOMAIN family: matching by domain name

  • DOMAIN: exact match on the full domain name; only matches that exact domain, not its subdomains.
  • DOMAIN-SUFFIX: matches a domain suffix and automatically covers all its subdomains — the most commonly used domain rule.
  • DOMAIN-KEYWORD: matches a keyword contained anywhere in the domain; the broadest scope, and also the most prone to false positives on unrelated domains.
DOMAIN,api.example.com,DIRECT
DOMAIN-SUFFIX,example.com,PROXY
DOMAIN-KEYWORD,analytics,REJECT

When all three rules above are present together, api.example.com matches both the first and second rules — but since the first rule comes first, api.example.com goes through DIRECT, while other subdomains of example.com are still handled by the second rule's PROXY. This is a textbook case of "narrower-scope rules go first."

IP-CIDR and IP-CIDR6: matching by IP range

Use IP-CIDR when the target is a known IP range rather than a domain name — common for private network addresses, fixed CDN origin IPs, or known provider ranges. By default this rule only applies to the connection's target IP. If you need to match against all A/AAAA records resolved for a domain, some cores offer a no-resolve option to skip that extra resolution overhead.

IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
IP-CIDR6,2001:db8::/32,DIRECT,no-resolve
CAUTION

Without no-resolve, Clash performs a DNS lookup on the domain before comparing it against the IP range, adding resolution overhead to every match. For rules that clearly don't need resolution — such as private-network ranges — always add no-resolve.

GEOIP: routing by country/region

GEOIP determines the outcome based on the country/region code of the target IP, commonly used for coarse-grained routing like "IPs in mainland China go direct, everything else goes through the proxy." Since it's matched against an entire country/region's IP database, it has the broadest scope and is usually placed near the end of the rule list.

GEOIP,CN,DIRECT
GEOIP,PRIVATE,DIRECT

RULE-SET and rule collections: batch-maintained routing fragments

Clash Meta (mihomo) supports the rule-providers field for importing external rule sets, referenced in the config via RULE-SET. This lets you maintain a category of sites (such as streaming services or ad-domain lists) in one place and update it periodically, instead of writing every entry by hand.

rule-providers:
  ads-reject:
    type: http
    behavior: domain
    url: "https://example.com/rules/ads.txt"
    path: ./rule-providers/ads-reject.yaml
    interval: 86400

rules:
  - RULE-SET,ads-reject,REJECT

MATCH: the catch-all rule

MATCH matches any connection not handled by an earlier rule and must be placed as the last line in the rule list, serving as the fallback exit for the entire routing table. It typically points to a policy group rather than a fixed node, so it can be switched later.

MATCH,PROXY

Rule ordering strategy: how to avoid rules overriding each other

When combining these rule types into a single config, arrange them top-down by "narrowest scope to broadest," roughly in four layers:

  1. Exact-exception layer: DOMAIN and private-network IP-CIDR entries, for specific addresses that need an individual policy — for example, sending an internal admin panel through DIRECT, or blocking a known malicious domain outright.
  2. Domain-grouping layer: mostly DOMAIN-SUFFIX, grouping domains by category (social, video, dev tools, etc.) into their corresponding policy groups.
  3. Rule-set and keyword layer: RULE-SET and DOMAIN-KEYWORD have broader scope, so place them after the domain-grouping layer to prevent keyword rules from prematurely intercepting domains that should be matched exactly.
  4. Region and catch-all layer: GEOIP handles remaining country/region-based routing, with MATCH as the final line.

The table below uses status badges to show the typical exit direction for several representative rules under this ordering scheme, useful for checking whether a config behaves as expected:

DOMAIN-SUFFIX,cn
DIRECT
DOMAIN-SUFFIX,googlevideo.com
PROXY
DOMAIN-KEYWORD,ad
REJECT
GEOIP,CN
DIRECT
MATCH
PROXY
NOTE

If a newly added custom rule doesn't seem to take effect, first check whether a broader rule above it is already catching the same target — this is the fastest way to diagnose an ordering issue, far faster than re-checking syntax.

Practical example: adding a custom fragment and choosing where to place it

Take the case of "route a specific dev-environment domain through DIRECT while keeping existing ad blocking and regional routing intact." The new rule should be inserted before the ad-blocking rule but after the domain-grouping layer, because its match scope is narrower and more specific than DOMAIN-KEYWORD and needs to be evaluated first:

rules:
  # Exact-exception layer
  - DOMAIN,192-168-1-1.local,DIRECT
  - IP-CIDR,172.16.0.0/12,DIRECT,no-resolve

  # New: dev-environment domain goes direct (inserted before the keyword rule)
  - DOMAIN-SUFFIX,dev.internal-project.com,DIRECT

  # Domain-grouping layer
  - DOMAIN-SUFFIX,github.com,PROXY
  - DOMAIN-SUFFIX,googlevideo.com,PROXY

  # Rule-set and keyword layer
  - RULE-SET,ads-reject,REJECT
  - DOMAIN-KEYWORD,analytics,REJECT

  # Region and catch-all layer
  - GEOIP,CN,DIRECT
  - MATCH,PROXY

If this new rule is mistakenly placed near MATCH, a broader keyword or regional rule above it will very likely have already caught the traffic, and the new rule will effectively never run — the symptom being "the config was saved, but traffic behavior didn't change."

Another common need is blocking a specific IP range instead of sending it direct — for example, blocking a batch of known probing servers:

rules:
  - IP-CIDR,203.0.113.0/24,REJECT,no-resolve
  - GEOIP,CN,DIRECT
  - MATCH,PROXY

This IP-CIDR,REJECT entry must come before GEOIP,CN,DIRECT. Otherwise, if that range happens to be classified as an IP in mainland China, it will be caught by the GEOIP rule and sent DIRECT first, and the block rule will never fire.

Common troubleshooting steps for rules that don't take effect

If a custom rule doesn't behave as expected, work through the following checks before assuming there's a syntax error:

  • Check its position: make sure no broader, earlier-matching rule sits above the new one — this is the most common cause.
  • Verify the rule type and format: DOMAIN-SUFFIX doesn't need a wildcard prefix (don't write *.example.com) — just the plain domain suffix, e.g. example.com.
  • Confirm the policy group name exists: the policy name at the end of a rule must exactly match a group name defined in proxy-groups or an actual node name — a typo can cause the core to error out or silently ignore that line when loading the config.
  • Check whether DNS-layer routing is involved: if fake-ip or a custom nameserver-policy is enabled, a domain may already be affected by DNS-based routing before it reaches the rule engine — inspect the DNS config section, not just rules.
STOP

Don't overuse DOMAIN-KEYWORD as a substitute for DOMAIN-SUFFIX. Keyword rules ignore domain structure and easily produce false matches on unrelated domains that happen to contain the same string (for example, the keyword ad would match adobe.com). When debugging unexpected connections, check first whether an overly broad rule like this is placed too early.

Get the client, verify your rules

Once your routing rules are set, load the config directly in your local client and check the connections panel to see which rule and exit node each connection actually matched.

Go to Downloads View Setup Guide
Get the ClientDL·711