What a Clash rule provider does—and what it does not
If you have copied domain rules from forum posts into a large Clash profile, you have probably met the maintenance problem: the main configuration becomes harder to review, updates are easy to forget, and a small rule change can affect unrelated routing. A Clash rule provider moves a collection of rules into a separately maintained file. Your main configuration then references that collection by name, just as it references a proxy group.
A provider is not a proxy, subscription, or automatic guarantee that a site will use a particular route. It supplies matching rules to Mihomo, the Clash-compatible core. The order of your main rules still decides which matching rule wins, and the final action still depends on the target proxy group or policy. Think of the provider as a reusable rule source, not a separate routing engine.
Rule providers commonly use one of three behaviors. domain is for domain-oriented entries, ipcidr is for IP ranges, and classical accepts the broader rule syntax used by the main rules list, such as domain, process, or network rules. The selected behavior tells the core how to interpret the file. A payload containing ordinary domain entries will not work as intended if you declare it as an IP range provider.
Providers can be local or remote. A local provider is useful while you are developing and testing a ruleset. A remote HTTP provider lets a client fetch an updated file from a URL, commonly a GitHub raw-content address. The client’s configuration names the provider, describes its behavior and format, and points to its file. The main rules list then uses a RULE-SET entry to send matching traffic to a policy group.
Before writing YAML, decide what the ruleset is responsible for. A focused list such as “services that should use my selected proxy group” is easier to reason about than a catch-all file containing unrelated streaming, advertising, LAN, and application rules. Small, clearly named providers make changes reviewable and reduce the chance that a new entry silently changes traffic for a different use case.
Build a small, reusable YAML ruleset
Start with a file that has one job and a visible owner. For example, create a custom-services.yaml file for domains that should follow a proxy group. With behavior: domain, keep the payload to domain-style entries. Do not add the top-level provider settings to the payload file: those settings belong in the main Mihomo configuration, while the payload contains the actual rules.
# custom-services.yaml
payload:
- DOMAIN,api.example.com
- DOMAIN-SUFFIX,example-cdn.com
- DOMAIN-KEYWORD,service-name
This example is deliberately small. DOMAIN matches the exact hostname, DOMAIN-SUFFIX covers a domain and its subdomains, and DOMAIN-KEYWORD is a broader text match. Prefer exact domains or suffixes when you know the hostnames involved. A keyword can match more than you intend, so use it only when you have checked likely false positives in the client’s connection log.
Now declare the provider in the main configuration. The example below assumes the file is served from a public GitHub repository. Replace the repository, branch, and path with your own values, and make sure the URL returns the raw YAML file rather than a GitHub HTML page.
rule-providers:
custom-services:
type: http
behavior: domain
format: yaml
path: ./providers/custom-services.yaml
url: "https://raw.githubusercontent.com/OWNER/REPOSITORY/main/rules/custom-services.yaml"
interval: 86400
rules:
- RULE-SET,custom-services,Proxy
- MATCH,DIRECT
The local path is where the client or core stores the fetched file. It is not the public download URL. Choose a path that is writable in your environment and consistent with the client’s configuration handling. The interval is expressed in seconds; 86400 asks for a daily refresh. A shorter interval is not automatically better: it increases fetch activity without improving a ruleset that changes only occasionally.
The position of RULE-SET matters. Mihomo evaluates rules from top to bottom and uses the first match. Put a provider before broad rules that would otherwise capture the same traffic, such as a general GEOIP or catch-all entry. If a domain is already matched by an earlier rule, adding it to a later provider will not change its route. Conversely, placing a broad provider too early can unexpectedly take traffic away from more specific policies.
If you need IP ranges or a mixture of rule types, choose the behavior and payload format deliberately. An IP provider should contain IP-CIDR-style entries and should be declared with behavior: ipcidr. A classical provider is appropriate when the list needs multiple supported rule forms. Do not label every provider “classical” by habit; a narrow behavior gives both maintainers and the core a clearer contract for the contents.
Host the rules on GitHub and make updates predictable
GitHub is convenient for a personal ruleset because it provides version history, readable diffs, and a stable place to review changes. Keep the rules in a dedicated file or directory, and make each commit describe the policy change rather than simply saying “update.” When a route changes unexpectedly, a useful commit history helps answer what changed, when it changed, and whether the new entry was intentional.
For a public repository, use the raw file URL in the provider configuration. Test that URL in a browser or with a command-line HTTP client: it should return the YAML content directly, not a repository page, a 404 page, or an access-denied message. Check spelling, capitalization, branch name, and directory path. Git paths are case-sensitive in many environments, so a file named Rules.yaml is not necessarily the same path as rules.yaml.
A private repository needs extra care. A normal raw URL is not a secure authentication mechanism, and embedding a personal access token in a Clash configuration can expose it through backups, screenshots, logs, or shared profiles. If the client cannot authenticate safely to your private source, use a hosting and access method that is appropriate for your threat model instead of making the repository public just to simplify fetching.
For a personal setup, following a branch such as main is usually convenient: a committed change becomes available to clients on their next refresh. For a managed group or a configuration that needs tightly controlled changes, consider pinning the URL to a reviewed commit or release asset. Pinning makes updates deliberate and reproducible, but it also means you must update the reference when you approve a new version. The right choice depends on whether automatic freshness or controlled rollout matters more.
Scheduled updates are useful when your rules genuinely change on a schedule, but a GitHub Actions workflow should not rewrite files just to create activity. A sensible workflow can validate YAML and rule structure on pull requests, then publish or promote an approved version. If a scheduled job generates a file from another source, review its output and failure behavior: an empty or malformed generated payload can be worse than a stale but valid file.
Do not assume every client refreshes a provider at the same moment. The configured interval, application state, core behavior, local cache, and network reachability can all affect when new content is loaded. After publishing a change, use the client’s provider update control if available, then confirm that the provider reports a successful refresh. A successful GitHub commit alone does not prove that the running core fetched or accepted the new ruleset.
Connect providers to real routing policies
A provider can feed any policy group that exists in your configuration, but the group name in RULE-SET must match an actual group. If the target group is named Proxy, use that exact name; if you use a selector such as Overseas, reference that instead. A mismatch can make the configuration invalid or leave the rule pointing at a policy that does not represent your intended route.
For example, separate work-related domains from a broader custom services list when they have a different routing requirement. The main rules section could put a specific work provider first, followed by the general provider, and finally a fallback:
rules:
- RULE-SET,work-services,Work
- RULE-SET,custom-services,Proxy
- GEOIP,LAN,DIRECT
- MATCH,DIRECT
This ordering illustrates a policy decision, not a universal template. A LAN rule may need to appear earlier if your environment has rules that would otherwise capture local destinations. Likewise, a broad provider may need to come after specific exceptions. Write down the intended precedence in a comment or in the repository’s README so that a future edit does not accidentally reverse it.
Keep provider names stable once they are used by multiple profiles. Renaming a YAML file does not necessarily require changing the provider’s identifier, but changing the identifier does require updating every RULE-SET reference. Use names that describe purpose rather than current implementation: work-services remains understandable even if the list’s source later moves from one repository to another.
For a domain that should bypass the proxy, you can create a separate provider and route it to DIRECT, or place a small explicit rule in the main configuration if it is a one-off exception. Separate it when the list is shared or maintained independently; keep it inline when it is a tiny, profile-specific exception. The aim is not to put every rule into a provider. It is to make the parts that change independently easy to maintain without obscuring policy priority.
Be cautious with broad entries. A suffix rule for a major hosting domain can include unrelated products, APIs, or user-controlled subdomains. A keyword rule can match a hostname you did not anticipate. Before routing a large domain family, inspect actual connections and test representative services. When several applications share a domain, domain-based routing cannot distinguish them by app unless you use a supported rule type and have verified that the relevant client and core expose that information.
Debug providers with evidence, not guesswork
When a rule does not appear to work, first separate three questions: did the provider load, did its entry match the connection, and did the selected policy route traffic as expected? These are different failure points. Repeatedly editing the YAML without identifying which stage failed can introduce new errors while leaving the original cause untouched.
- Check loading: inspect the client’s configuration validation and provider status. Look for download failures, parse errors, an unexpected content type, or a missing local file.
- Check the payload: open the fetched file and confirm that it contains the expected entries in the format declared by
behaviorandformat. - Check precedence: find the connection in the client’s log and review the matched rule. An earlier rule may be winning before the provider is reached.
- Check the policy: verify that the referenced group exists, has a usable selection, and is not itself set to a route you did not expect.
- Check the hostname: compare the hostname shown in the connection details with the exact domain, suffix, or keyword in your payload.
If a provider works after switching to Global mode but not in rule mode, the evidence points toward rule ordering, matching, or policy selection—not necessarily a broken download. Global mode is a diagnostic comparison, not a good permanent substitute for correcting the rules. Return to the intended mode after testing and make one controlled change at a time so you can tell which adjustment affected the result.
Remember that applications may contact several hostnames for one visible feature. A website can load its page from one domain and fetch images, authentication, APIs, or updates from others. Add only hostnames supported by observed connections or reliable service documentation. Then test the feature again, because a rule for the main page alone may leave secondary requests on a different route.
After changing a ruleset, force a provider refresh where the client allows it, and then generate a new connection. Existing connections may remain open and continue using the route chosen when they were created. If the log still shows stale behavior, restart the affected request or application before concluding that the provider failed. Save a known-good version of the profile so that a syntax mistake or overly broad rule can be rolled back quickly.
Some clients expose provider controls and logs differently, and a core version may support options that an older bundled core does not. If a configuration parses on one device but not another, compare the Mihomo versions and the exact error output before changing the ruleset to accommodate an assumed incompatibility. Keep credentials and private subscription URLs out of diagnostic screenshots, and share only the relevant rule names and sanitized log lines when asking for help.
Compared with tools that expose only a single global on/off switch or keep every exception buried in a large profile, Clash-compatible clients let you inspect matched rules, separate reusable providers, and direct each set to a deliberate policy group. That structure does require careful ordering, but it also makes changes reviewable and troubleshooting more concrete. If you want to test the same provider-based workflow in a supported client, start with the download page and choose the build that fits your platform.