Writing hundreds of DOMAIN-SUFFIX rules by hand is impractical, and it cannot keep up with how sites change. rule-providers lets you reference externally maintained lists that refresh on a schedule.
The basic structure
Two steps: define the provider, then reference it from rules.
rule-providers:
cn-domain:
type: http
behavior: domain
format: text
url: "https://example.com/rules/cn-domain.txt"
path: ./ruleset/cn-domain.txt
interval: 86400
proxy: DIRECT
rules:
- RULE-SET,cn-domain,DIRECT
- MATCH,PROXYField by field
| Field | Values | Meaning |
|---|---|---|
type | http / file / inline | Remote, local file, or written inline |
behavior | domain / ipcidr / classical | Must match the file's contents |
format | text / yaml / mrs | File format |
url | Address of the rule file | Required when type is http |
path | Local cache path | Relative to the config directory |
interval | Seconds | Update interval; 86400 = one day |
proxy | Policy name | Which route to use when downloading the file |
behavior: the three types are not interchangeable
This is where things go wrong most often — an incorrect behavior produces no error, the rules just silently do nothing.
domain
The file looks like this:
google.com
+.youtube.com
www.github.comThe +. prefix is equivalent to DOMAIN-SUFFIX (all subdomains included); without it the match is exact.
ipcidr
1.0.1.0/24
8.8.8.8/32
2001:4860::/32classical
DOMAIN-SUFFIX,google.com
DOMAIN-KEYWORD,youtube
IP-CIDR,8.8.8.8/32,no-resolve
PROCESS-NAME,Telegram.exeNote: a classical file contains no policy names. The policy comes from the RULE-SET line that references it.
format: text, yaml and mrs
text: plain text, one entry per line, # starts a comment. The most common.
yaml:
payload:
- google.com
- "+.youtube.com"mrs: a binary format specific to Mihomo, small and fast to load. It supports only domain and ipcidr, not classical.
For very large rule sets (tens of thousands of entries and up), prefer the mrs version where the source provides one.
interval: how often to update
| Kind of rule set | Suggested interval |
|---|---|
| Regional domain / IP lists | 86400 (one day) |
| Ad-blocking lists | 86400 |
| Streaming unlock lists | 43200 (half a day) |
| Small lists you maintain yourself | 3600 or longer |
Setting it very short achieves nothing — these lists do not change several times a day, and you just add load to whoever hosts them.
A complete working configuration
A combination of rule sets that covers the common needs:
rule-providers:
# ad blocking
reject:
type: http
behavior: domain
format: text
url: "https://example.com/reject.txt"
path: ./ruleset/reject.txt
interval: 86400
proxy: DIRECT
# domains that should go direct
direct:
type: http
behavior: domain
format: text
url: "https://example.com/direct.txt"
path: ./ruleset/direct.txt
interval: 86400
proxy: DIRECT
# domains that need a proxy
proxy:
type: http
behavior: domain
format: text
url: "https://example.com/proxy.txt"
path: ./ruleset/proxy.txt
interval: 86400
proxy: DIRECT
# in-region IP ranges
cncidr:
type: http
behavior: ipcidr
format: text
url: "https://example.com/cncidr.txt"
path: ./ruleset/cncidr.txt
interval: 86400
proxy: DIRECT
# loopback and private ranges
lancidr:
type: http
behavior: ipcidr
format: text
url: "https://example.com/lancidr.txt"
path: ./ruleset/lancidr.txt
interval: 86400
proxy: DIRECT
rules:
- RULE-SET,lancidr,DIRECT,no-resolve
- RULE-SET,reject,REJECT
- RULE-SET,proxy,🚀 Select
- RULE-SET,direct,DIRECT
- RULE-SET,cncidr,DIRECT,no-resolve
- GEOIP,CN,DIRECT
- MATCH,🐟 FallthroughMind the order: private → blocking → proxy → direct → IP ranges → geographic catch-all → MATCH.
The proxy list goes before the direct list because some domains appear in both, and giving the proxy list priority matches what people expect.
inline: small rule sets written in place
When you do not want a separate file for three entries:
rule-providers:
my-work:
type: inline
behavior: domain
payload:
- "+.mycompany.com"
- "+.internal.corp"
- "gitlab.internal"file: using a local file
For rules you maintain yourself:
rule-providers:
personal:
type: file
behavior: classical
path: ./ruleset/personal.listWith interval set, some core versions re-read the file periodically. Reloading the config after editing is the reliable route.
What to check when a rule set does nothing
Inspect the cached file
The file named by path sits under your config directory; just open it:
- File missing → download failed; check the url and the proxy setting
- File contains HTML → the url returned a web page (a 404 page, for instance); the address is wrong
- File looks fine but rules do nothing → behavior mismatch
On Windows the config directory is usually:
%APPDATA%\io.github.clash-verge-rev.clash-verge-rev\Read the log
Switch the log level to debug and search for the provider name; you will see the whole download-and-load sequence:
INFO Start initial provider cn-domain
INFO Provider cn-domain loaded, 12043 rulesSeeing loaded, N rules means it worked. If N is 0, the behavior or format is wrong.
Performance considerations
If your rule sets total more than a hundred thousand entries and the device is modest (a soft router, an old phone), consider:
- Switching to the mrs format
- Dropping rule sets you do not use (streaming platforms you never watch)
- Replacing some domain lists with
GEOSITE, which uses the core's built-in database and needs no extra download
In short
- behavior must correspond to the file contents; get it wrong and it fails silently
- Add no-resolve when referencing IP-based rule sets
- Give each provider a
proxyfield so updates do not fail - Rule-set order: private → blocking → proxy → direct → IP → GEOIP → MATCH
- When nothing happens, first look at what is actually in the cached file
Next, a practical build: company intranet, direct local access and overseas proxy coexisting.
Related docs
Syntax, matching behaviour and relative cost for every rule type, what no-resolve actually does, why rule order decides everything, and how to find which rule a connection matched.
A complete configuration that serves a corporate VPN intranet, direct local access and proxied overseas services at the same time, covering domains and IP ranges, DNS routing, and the conflicts that come up.
The complete picture for per-process routing: enabling find-process-mode, finding the process name, PROCESS-NAME versus PROCESS-PATH, the performance cost, and why a rule you wrote does nothing.