How the Rule Engine Actually Works

At its core, Clash (and cores such as mihomo) implements policy-based routing: every outbound TCP connection and many DNS lookups are classified against your rules array from top to bottom. The first matching rule wins; nothing below it is evaluated for that flow. The right-hand side of each rule is either a built-in action such as DIRECT or REJECT, or the name of a proxy-groups entry that further resolves to a concrete proxy node.

That single fact drives most “why is this site not using the proxy I expected?” questions. If you put a broad GEOIP,CN,DIRECT line above your streaming rules by mistake, domestic IPs will always hit DIRECT before your domain rules are ever considered—unless those domains were resolved and matched earlier in the list. Order is not cosmetic; it is the policy. For a broader overview of client modes and documentation, see our Clash tutorials and documentation.

Think of rules as an ordered checklist. You typically want the most specific lines first (exact domains, LAN, ad block lists), then regional GeoIP catches, and finally a MATCH rule that sends everything else to your default proxy group.

The closing line is almost always MATCH,<group-or-action>, which acts as a catch-all for anything that did not match earlier. Some third-party generators label that concept “final rule” in comments, but the YAML keyword is still MATCH in standard Clash-family configs. On supported platforms you may also see PROCESS-NAME rules that route traffic based on the originating executable; that is powerful for per-app split tunneling on desktops when combined with TUN or correct system integration, but it is platform-specific and should be tested per OS build.

Domain Rules: Precision vs. Maintenance

Domain-based rules are the most intuitive way to steer traffic because they align with how users think about services: “send all Google traffic through the proxy” or “keep my bank on DIRECT.” Clash offers several matchers, each with different cost and specificity.

  • DOMAIN — Matches a single fully qualified hostname. Use for one-off exceptions where you need exact control.
  • DOMAIN-SUFFIX — Matches the hostname if it equals the suffix or ends with . plus the suffix. This is the workhorse for entire sites and CDNs that share a common domain suffix.
  • DOMAIN-KEYWORD — Matches if the hostname contains the keyword. Powerful but easy to over-broad; a short keyword can accidentally capture unrelated hosts.
  • DOMAIN-REGEX — Full regular expressions on the hostname. Flexible and expensive; reserve for advanced cases and keep patterns tight.

In practice, subscription authors and community rule sets rely heavily on DOMAIN-SUFFIX because it balances coverage and predictability. DOMAIN-KEYWORD appears in blocklists where many unrelated subdomains must be caught, but for routing policy it is usually better to prefer explicit suffix lists or external RULE-SET providers so you can update logic without editing hundreds of inline lines.

# Examples (illustrative — adjust group names to your config)
rules:
  - DOMAIN,private.example.com,DIRECT
  - DOMAIN-SUFFIX,google.com,Proxy
  - DOMAIN-KEYWORD,youtube,Proxy
  - DOMAIN-REGEX,^speed\.test\.net$,DIRECT

IP-CIDR, SRC-IP-CIDR, and GEOIP

When the destination is already an IP address, or when you want to match before or after DNS, IP-based rules matter. IP-CIDR and IP-CIDR6 match destination addresses against CIDR blocks. They are essential for LAN ranges, VPN interfaces, and pinning specific subnets to DIRECT regardless of domain.

SRC-IP-CIDR matches the source address of the connection—useful on gateways or when multiple devices share a Clash instance and you need per-subnet policies. Less common on a single laptop, but invaluable on routers running a Clash-based stack.

GEOIP uses a MaxMind-style GeoIP database (often geoip.dat or Country.mmdb) to map the destination IP to a country or region code, then matches against the rule. A typical pattern is GEOIP,CN,DIRECT to send Chinese IPs direct, reducing latency and billing on domestic content. Accuracy depends on database freshness; stale files misclassify CDN edges and cause surprising paths. Modern cores support geodata-mode and remote update URLs—keep them on a sensible refresh interval.

One subtle interaction: GeoIP runs on IPs. If you use fake-ip in DNS, the core may not know the real destination IP until connection time, depending on implementation details and rule types. Misordered rules or missing fake-ip-filter entries for local domains can make GeoIP behave inconsistently for some flows. Aligning DNS mode, fake-ip-filter, and rule order is part of tuning a “quiet” config that does what you expect without constant log diving.

RULE-SET and rule-providers

Inline rules do not scale. Maintaining ten thousand lines inside config.yaml is slow to parse and painful to merge. rule-providers (often paired with RULE-SET in the rules section) load external lists—domain, ipcidr, or classical text—from HTTP URLs or local files, with optional periodic refresh. Community-maintained lists for ads, malware, China IP ranges, and global services become one-liners in your config.

rule-providers:
  cn:
    type: http
    behavior: domain
    url: "https://example.com/rules/cn_domain.yaml"
    path: ./ruleset/cn_domain.yaml
    interval: 86400

rules:
  - RULE-SET,cn,DIRECT
  - MATCH,Proxy

Startup time and memory improve because the core can optimize internal structures for large sets. When you adopt rule providers, treat their interval as a trade-off: frequent updates keep accuracy high but increase background fetch activity; long intervals reduce churn on metered links.

Proxy Groups: select, url-test, fallback, load-balance, relay

Raw proxies entries are concrete servers. proxy-groups wrap them in strategies so the user—or the core—chooses which node to use. Getting groups right is what makes daily browsing feel automatic instead of fragile.

select

type: select presents a manual choice: you pick the member in the UI, and all traffic routed to that group uses the selected proxy until you change it. Best for “which country exit do I want today?” and for grouping specialized nodes (streaming, gaming) under readable names.

url-test

type: url-test periodically measures latency to a configurable URL (often an HTTP endpoint) and picks the fastest healthy member. Parameters such as interval, tolerance, and lazy control how aggressively the core switches. This is the default choice for “always use the quickest node in my subscription” without micromanagement. Set the test URL to something stable and geographically neutral for your use case; exotic URLs may flap when the remote server has a bad day.

fallback

type: fallback walks members in order and sticks to the first that passes health checks—classic failover behavior. It differs from url-test: order is explicit, not purely latency-ranked. Use when you have a primary chain and backups that should only activate when the primary fails.

load-balance

type: load-balance spreads connections across members (implementation details vary by core version). Useful when you have multiple equivalent exits and want to reduce per-node load; less common on single-user laptops than on high-throughput setups.

relay

type: relay chains proxies in sequence (A then B). Required for nested scenarios such as fronting a slow protocol through a faster hop. Misconfiguration here is a common source of “connected but no data” issues—verify each hop independently before chaining.

proxy-groups:
  - name: Proxy
    type: select
    proxies:
      - Auto
      - DIRECT
      - node-hk-01
  - name: Auto
    type: url-test
    url: http://www.gstatic.com/generate_204
    interval: 300
    tolerance: 50
    lazy: true
    proxies:
      - node-jp-01
      - node-sg-01
      - node-us-01

Best Practices That Survive Real Networks

First, put LAN and loopback exceptions near the top: DOMAIN-SUFFIX,local,DIRECT, private IP-CIDR ranges, and captive portal domains if you use them. Nothing is more frustrating than a proxy intercepting traffic that should never leave the machine.

Second, separate “policy intent” from “node quality.” Use narrow domain or RULE-SET lines for intent (“this service uses Proxy”) and let url-test groups handle which specific node is fastest. Mixing dozens of per-domain exceptions with five overlapping GeoIP lines creates configs that nobody—including you—can reason about six months later.

Third, refresh GeoIP and geosite data on a schedule that matches how much you care about accuracy. CDNs shift; country boundaries in databases update. A yearly file copied from an old gist is a common reason domestic video works through Tokyo one week and breaks the next.

Fourth, log with purpose. When debugging, temporarily raise log level and watch which rule hits. Cores that expose a web UI often show the matched policy per connection—use that instead of guessing from YAML alone.

Prefer RULE-SET over pasting huge lists into rules. Your file stays readable, updates are atomic, and you reduce merge conflicts when combining subscription snippets from multiple sources.

Putting It Together

Rule-based splitting is not magic: it is disciplined ordering of matchers from specific to general, backed by fresh geo data and proxy groups that match how you actually use the network. Nailing that structure pays off in lower latency, fewer leaks, and configs you can still edit after a year. Compared with all-or-nothing global VPN toggles, a well-tuned Clash policy keeps local and trusted traffic direct while sending only what needs egress through your chosen path—precisely the balance power users expect in 2026.

If you are setting up a client for the first time or replacing an older GUI, a modern Clash build with clear group management and sane defaults saves hours of YAML archaeology. Our client is built around that workflow so you spend time on policy—not on hunting down typos in twenty nested includes.

→ Download Clash for free and experience the difference on Windows, macOS, Android, iOS, or Linux.