Why custom rule providers are worth maintaining
Many Clash subscriptions arrive as complete, opaque configurations. They may contain hundreds or thousands of rules, but the user often cannot tell which list controls developer tools, container registries, software downloads, or AI services. When a service changes its domain structure, the subscription provider may update the list later than expected, or may place a newly added rule in a broad policy group that does not match your preferred routing strategy.
A custom rule provider separates routing data from the main configuration. Instead of repeatedly editing the subscription-generated YAML, you maintain small, targeted rule files and reference them through rule-providers. The main configuration defines how a provider is downloaded and refreshed; the provider file contains the actual domains or Clash rules. This separation makes changes easier to review, reuse, and roll back.
GitHub is useful for this workflow because a repository can preserve file history, show the difference between revisions, and provide a stable raw-content address for the Clash core to download. The repository does not need to contain your proxy nodes or subscription token. A public repository should contain only non-sensitive routing data, such as domain names and rule comments. Credentials, private subscription URLs, private endpoints, and access tokens must never be committed to a repository.
| Component | Responsibility | Typical change frequency |
|---|---|---|
| Main configuration | Defines providers, policy groups, DNS, ports, and rule order | Low |
| Rule provider file | Contains domains, IP networks, or complete Clash rules | Medium to high |
| Git repository | Stores versions, review history, and published file content | Whenever a list is changed |
| mihomo or Clash client | Downloads, caches, parses, and applies the provider | At startup or on the configured interval |
Choose the provider behavior before writing YAML
The behavior field tells the core what kind of data it is loading. The most important choices are domain, ipcidr, and classical. Selecting the wrong behavior is a common reason for an apparently successful download to produce no matching traffic.
| Behavior | Payload contains | Suitable examples |
|---|---|---|
domain |
Domain names or domain suffixes | AI websites, package mirrors, developer platforms |
ipcidr |
IPv4 or IPv6 CIDR ranges | Known network ranges, infrastructure address blocks |
classical |
Complete Clash rule expressions | Mixed rules using domain, keyword, IP, port, or process matching |
Use a domain provider for readable domain lists
A domain provider is the simplest option when the routing decision depends on the requested hostname. With a YAML-formatted provider, the payload is normally stored under payload. Domain suffix entries beginning with + are useful when the service uses several subdomains. A suffix entry such as +.example.com represents the base domain and its subdomains according to the provider behavior supported by the core.
payload:
- "+.docker.com"
- "+.docker.io"
- "+.ghcr.io"
- "+.pypi.org"
- "+.npmjs.org"
Keep the file limited to domain data when the provider behavior is domain. Do not place a line such as DOMAIN-SUFFIX,example.com in this file and expect it to be interpreted as a domain-only payload. That line is a complete Clash rule and belongs in a classical provider instead.
Use a classical provider for mixed rules
A classical provider is more flexible because every payload entry is a full rule. It can combine domain suffixes, domain keywords, exact domains, IP ranges, ports, and other rule types supported by the selected core. The trade-off is that the file becomes more tightly coupled to Clash or mihomo rule syntax.
payload:
- DOMAIN-SUFFIX,openai.com
- DOMAIN-SUFFIX,anthropic.com
- DOMAIN-SUFFIX,githubcopilot.com
- DOMAIN-KEYWORD,claude
- DOMAIN-SUFFIX,api.example.ai
Use classical behavior when the list needs different matching methods or when it must preserve rules copied from an existing configuration. If the list only contains domain suffixes, the domain behavior is easier for another person to understand and less likely to contain an accidental policy or rule-type error.
Build a GitHub-hosted YAML file
Organize files for review and rollback
Create one file per purpose rather than placing every rule in a single document. A practical repository layout might contain directories such as rules/ai, rules/developer, and rules/containers. File names should use lowercase letters, numbers, and hyphens. Names such as ai-services.yaml and container-registries.yaml are easier to reference than files containing spaces or localized punctuation.
Use a consistent ordering strategy. Alphabetical ordering is simple and produces small, predictable diffs. If a service has a base domain and several dedicated API domains, keep them adjacent. Avoid adding a domain merely because it appears in a browser request once. First confirm that it belongs to the service and that routing it through the selected policy is desirable.
payload:
- "+.anthropic.com"
- "+.claude.ai"
- "+.openai.com"
- "+.oaistatic.com"
- "+.openrouter.ai"
GitHub-hosted files should be plain text with valid UTF-8 encoding. Avoid smart quotes, tabs used for indentation, hidden editor metadata, and HTML copied from a web page. A public raw-content endpoint should return the file itself, not a repository page, login page, redirect notice, or formatted preview. If the core receives HTML instead of YAML, the provider may fail with a parser error even though the URL appears to open correctly in a browser.
Publish with a stable content address
The provider URL must point to raw file content. In the repository interface, use the file’s raw-content option and copy that address into the Clash configuration. Avoid using a normal repository page URL. The normal page is designed for browsers and may return HTML, while the raw address returns the YAML bytes that mihomo expects.
For routine development, a branch-based address is convenient because every commit becomes available without changing the main configuration. For a high-stability setup, pin the provider to a commit-specific address or release-specific path. A branch can change at any time; a commit reference allows you to test a revision before switching production clients.
| Publishing method | Advantage | Risk or limitation |
|---|---|---|
| Default branch | Simple to maintain and update | A bad commit affects clients after the next refresh |
| Commit-specific path | Reproducible and easy to roll back | Requires editing the main configuration for each approved revision |
| Release or version path | Clear separation between tested versions | Requires a release or file-publishing workflow |
Reference providers from the main configuration
The main YAML configuration needs a rule-providers section and corresponding RULE-SET entries in the top-level rules list. The provider key is local to your configuration. It should be short, descriptive, and used consistently in both places.
rule-providers:
ai-services:
type: http
behavior: domain
format: yaml
path: ./ruleset/ai-services.yaml
url: https://raw-content.example/your-account/your-repo/main/rules/ai-services.yaml
interval: 86400
container-registries:
type: http
behavior: domain
format: yaml
path: ./ruleset/container-registries.yaml
url: https://raw-content.example/your-account/your-repo/main/rules/containers/container-registries.yaml
interval: 43200
rules:
- RULE-SET,ai-services,AI Services
- RULE-SET,container-registries,Developer Proxy
- MATCH,Final
The example uses a placeholder host to avoid turning a documentation example into a live dependency. Replace each url with the raw-content address generated by your repository. The local path is the cache location used by the core. Keep it distinct for every provider; if two providers share the same path, one downloaded file can overwrite the other or produce confusing startup behavior.
Understand each provider field
| Field | Purpose | Practical guidance |
|---|---|---|
type |
Defines how the provider is obtained | Use http for a remotely hosted file |
behavior |
Defines how payload entries are interpreted | Match it to domain, ipcidr, or classical |
format |
Defines the file encoding structure | Use yaml for a payload document; use text only for supported line-based lists |
path |
Stores the downloaded provider locally | Use a separate relative path for each provider |
url |
Points to the remote provider content | Use the raw file address, not a repository web page |
interval |
Sets the refresh interval in seconds | Use 43200 for twice daily or 86400 for daily updates |
The provider update interval is independent from the subscription update interval. A subscription can refresh every 24 hours while a frequently changing provider refreshes every 12 hours. Very short intervals increase requests and can trigger rate limits without improving routing quality. For manually maintained files, 86400 seconds is usually sufficient; for a list that changes several times per day, 21600 or 43200 seconds may be reasonable.
Place rule-set entries in the correct order
Clash evaluates rules from top to bottom and stops at the first match. A broad rule placed before a custom provider can prevent the provider from ever being reached. For example, if GEOIP,CN,DIRECT appears above a provider containing a specific developer service, the IP-based rule may win before the domain provider is evaluated. Put targeted providers above broad geographic, fallback, or catch-all rules when the targeted routing decision should take priority.
rules:
- RULE-SET,ai-services,AI Services
- RULE-SET,container-registries,Developer Proxy
- DOMAIN-SUFFIX,example.local,DIRECT
- GEOIP,CN,DIRECT
- MATCH,Final
The policy name after the provider key must already exist in proxy-groups or be a valid built-in policy such as DIRECT or REJECT. A provider can download correctly while traffic still fails because the policy name is misspelled, the group contains no usable proxy, or the selected group is currently unavailable.
Test, debug, and maintain the workflow
Validate the remote file before importing it
Test the raw address outside the Clash client first. Confirm that the HTTP response is successful, the response body is the expected YAML, and the file is not empty. On a desktop system, a command-line request can show the status code and save a local copy for inspection. Do not paste an address containing private credentials into a shared terminal history or support ticket.
curl -L --connect-timeout 10 --max-time 30 \
-o provider.yaml \
-w "HTTP=%{http_code} SIZE=%{size_download} TYPE=%{content_type}\n" \
"https://raw-content.example/your-account/your-repo/main/rules/ai-services.yaml"
Open the downloaded file and check that it begins with a valid YAML structure such as payload:. If the body starts with <!doctype html>, an error document was returned. If the file contains a list but no payload key while using YAML provider format, the parser may reject it or the provider may contain no usable rules.
Check client logs and provider status
After importing the configuration into FlClash or another mihomo-based client, inspect the provider status page if the client exposes one. Look for the last update time, rule count, download error, and local cache path. A successful provider update confirms only that the file was downloaded and parsed. It does not prove that a request will match the provider or that the selected policy can connect.
- Download failed: Check DNS, TLS access, the raw-content address, and whether the update request should use a proxy.
- YAML parse error: Check indentation, quotation marks, encoding, and whether the response is actually HTML or JSON.
- Provider has zero rules: Compare
behavior,format, and the payload structure. - Rule count is correct but traffic uses the wrong policy: Check top-to-bottom rule order and the spelling of the policy group.
- Only some requests match: Inspect the application’s actual hostnames, API domains, redirects, and DNS behavior.
- Rules work in system proxy mode but not TUN mode: Check DNS mode, sniffing settings, fake-IP behavior, and whether the application bypasses the virtual interface.
Maintain lists with small, reviewable commits
Every rule should have a reason to exist. Use commit messages such as “Add package mirror domain” or “Remove retired API hostname” rather than generic messages like “update rules.” Review the difference before publishing. A typo in a suffix can route unrelated services, while an overly broad entry such as +.cloudfront.net can affect thousands of unrelated websites.
Test additions with the actual application. For a container registry, test image metadata lookup, authentication, manifest retrieval, and layer downloads because those operations may use different domains. For an AI service, test the web interface, API endpoint, static assets, and streaming response separately. For a developer platform, test sign-in, repository access, package downloads, and notification endpoints. Add only the domains that genuinely need the selected policy.
When a provider causes a problem, revert the repository commit or temporarily remove its RULE-SET entry. Keeping a known-good local cache and a previous commit gives you a recovery path even when the remote service changes unexpectedly. Once the corrected file is verified, restore the provider and force an update from the client.
A reusable production checklist
Before relying on a GitHub-hosted provider for daily traffic, review the complete chain from repository file to final policy. The following checklist covers the failures that are easiest to miss when the configuration appears to load normally.
- Confirm that the repository contains no subscription tokens, private keys, passwords, or exported node definitions.
- Confirm that the client uses a mihomo or another core version compatible with the provider syntax.
- Use a raw-content URL that returns the file itself rather than a repository HTML page.
- Choose
domain,ipcidr, orclassicalaccording to the actual payload. - Set
format: yamlonly when the file uses the expected YAML payload structure. - Give every provider a unique local
path. - Place targeted
RULE-SETentries above broad rules that could match first. - Verify that every referenced policy group exists and has a usable selected proxy.
- Choose a sensible refresh interval instead of refreshing on every startup or every few minutes.
- Test DNS, system proxy mode, and TUN mode separately when troubleshooting mismatches.
- Keep changes small so a faulty revision can be identified and reverted quickly.
Custom rule providers are most effective when they remain small, explicit, and independent from the subscription’s generated content. The subscription can continue to provide nodes and standard policy groups, while your repository supplies focused routing knowledge for the services that matter to your workflow. With correct behavior selection, raw-content publishing, careful rule order, and a tested rollback path, GitHub-hosted YAML becomes a maintainable extension of a Clash configuration rather than another opaque dependency.