Plate B-01Scope of This Page and How to Read It
This site has two tracks. The setup guide is the fast-track path: install the client → import a subscription → pick a mode → verify the connection — follow that order and you're up and running, no config-file reading required. This page is the other track: a reference to come back to whenever you actually need to open config.yaml — say, to figure out why a rule isn't matching, add a few custom routing rules on top of a subscription, or hand-write a minimal config from scratch. The two tracks point at each other: if this is your first time with Clash, finish the guide first, then come back here and look things up by field number once your setup is working.
One thing worth clarifying up front: mainstream GUI clients today — Clash Plus, Clash Verge Rev, FlClash, and others (full list on the downloads page and client comparison page) — all run on the mihomo core or a compatible core, and all read the same YAML config format. This page covers fields recognized across cores; a handful of extensions only supported by newer cores are called out individually in their sections, so you don't copy something into an older core and get an "unrecognized field" load error.
Suggested reading path: changing routing rules? Jump straight to Plate B-07 and B-08. Manual edits disappearing every time the subscription refreshes? See Plate B-09. A single node won't connect? Read the node timeout checklist before touching the config. Nothing connects at all? Start with the FAQ page and the setup guide to check the client and system proxy state first — don't edit config files while things are actively broken.
example.com, your-password, etc.) meant only to show field structure — none of it will actually connect. Use the real values from your subscription or self-hosted server.
Plate B-02YAML Structure Overview
Three Non-Negotiable Rules
config.yaml is a standard YAML document made up of top-level keys, each one owning a subsystem. YAML has three hard rules, and nearly every "config failed to load" error traces back to breaking one of them. First: indentation must use spaces only — two spaces per level by convention — never Tab characters. Second: the colon in "key: value" needs a space after it; port:7890 gets parsed as one literal string, not a key-value pair. Third: list items start with "- ", and dashes at the same level must line up in the same column.
Quoting strings correctly matters too: any value containing #, a colon, curly braces, or starting with a special character needs to be wrapped in quotes — otherwise everything after # gets treated as a comment and cut off. If a node name has an emoji, a space, or full-width characters, quote it every time to avoid parsing ambiguity, and reference it with the exact same quoting wherever it's used in a proxy group.
Top-Level Key Index
| Top-Level Key | Type | Role | Related Plate |
|---|---|---|---|
port / socks-port / mixed-port | Integer | Local proxy listening port | B-03 |
allow-lan / bind-address | Boolean / String | Whether to accept LAN requests and which address to bind | B-03 |
mode | Enum | Working mode: rule / global / direct | B-03 |
log-level | Enum | Log verbosity | B-03 |
external-controller / secret | String | Control API address and access secret | B-03 |
dns | Map | Core-level DNS resolution behavior | B-04 |
tun | Map | Virtual network adapter takeover (best toggled from the client UI) | B-03 |
proxies | Array | Proxy node definitions | B-05 |
proxy-groups | Array | Proxy groups (outbound grouping) | B-06 |
rules | Array | Routing rules, matched top to bottom | B-07 |
rule-providers | Map | External rule set sources | B-08 |
Minimal Working Skeleton
Below is a complete minimal config skeleton — with all five parts in place, the core will load it. Each plate ahead zooms into one section in detail:
mixed-port: 7890
allow-lan: false
mode: rule
log-level: info
dns:
enable: true
enhanced-mode: fake-ip
nameserver:
- https://doh.pub/dns-query
proxies:
- name: "example-node"
type: ss
server: example.com
port: 8388
cipher: aes-128-gcm
password: "your-password"
proxy-groups:
- name: "Proxy Select"
type: select
proxies:
- "example-node"
- DIRECT
rules:
- GEOIP,CN,DIRECT
- MATCH,Proxy Select
Recommended order for reading an unfamiliar config: check mode and the port section first to see how it's set up to run, then read proxy-groups to understand how outbounds are organized, and finally check rules to see where traffic actually goes. The proxies section is usually auto-generated by a subscription and only needs a close read when you're troubleshooting a single node.
Plate B-03General Fields: Ports, Mode, and the Control API
The Three Port Fields
port is the plain HTTP proxy port, socks-port is the SOCKS5 port, and mixed-port serves both protocols on the same port. Modern clients default to mixed-port: 7890 — point your system proxy, browser proxy settings, and terminal environment variables all at this one port; there's no need to configure them separately. Setting any of the three to 0, or omitting it, disables that port. One thing to watch: if you change the port number, your system proxy settings need to match, or you'll get the classic false alarm of "the client is running but no traffic is coming through" — see the system proxy troubleshooting guide for the full checklist.
Sharing Over LAN
Setting allow-lan: true lets other devices on your network use this machine as their proxy server — handy for pointing a smart TV box or game console (that can't run a client itself) at a proxy exit. Pair it with bind-address to restrict listening to a specific network interface. On public networks like coffee shops or airports, keep this false — otherwise anyone on the same network segment can ride your proxy.
Working Mode: mode
| Value | Behavior | Typical Use |
|---|---|---|
rule | Match traffic against the rules list to pick an outbound | Everyday default — direct for local traffic, proxy for the rest |
global | All traffic goes through one chosen proxy group, rules are ignored | Quickly test a node, or sanity-check when rules look suspicious |
direct | All traffic goes direct, bypassing the proxy entirely | Temporarily disable the proxy without closing the client |
Logging and the Control API
log-level ranges from quiet to verbose: silent / error / warning / info / debug. Leave it at info day to day; switch to debug only when checking whether a specific rule is matching — the core will print the match result for every connection — then switch back, since running debug long-term generates a lot of log noise. external-controller sets the listening address for the RESTful control API (commonly 127.0.0.1:9090); every GUI client's connections panel, node switching, and latency tests all read and write core state through it. secret is the access token for that API. With ipv6: false, the core neither resolves nor connects to IPv6 addresses — worth keeping off if your network's IPv6 path is flaky.
mixed-port: 7890
allow-lan: false
bind-address: "*"
mode: rule
log-level: info
ipv6: false
external-controller: 127.0.0.1:9090
secret: "xxxx"
There's also a larger top-level key, tun: it takes over all system traffic via a virtual network adapter, which fixes apps that ignore the system proxy setting. Its sub-fields vary quite a bit between core versions and touch on system permissions, so it's best to use the "TUN mode" toggle in your client's UI and let it generate the right fields rather than hand-writing them; see the relevant entry on the FAQ page for what to check before turning it on.
allow-lan and external-controller open something up to the outside: the former exposes the proxy port, the latter exposes the control API. Unless you specifically need cross-device access, keep the listening address at 127.0.0.1 and set a secret on the control API.
Plate B-04DNS Config: The Foundation Routing Rules Depend On
Why DNS Needs Its Own Section When Proxying
Most rule matching happens at the domain level. If a domain gets resolved to a poisoned IP by your local network before it ever reaches the core, then IP-based rules like GEOIP will misjudge accordingly — the symptom is "the rule looks correct, but traffic still goes out the wrong exit." The dns section exists to let the core take over resolution itself, so whatever result the rules are matching against is actually trustworthy. enable: true is the switch that turns this whole section on.
Two enhanced-mode Options
In fake-ip mode, the core hands back a placeholder address from a reserved range (198.18.0.1/16 by default) for every domain, and only resolves the real address once a connection is actually being made and a rule has matched. The upside is skipping an extra resolution round-trip for lower latency, and sidestepping local DNS tampering entirely — it's the current default recommendation. redir-host mode returns real IPs and is more broadly compatible, at the cost of one extra resolution per connection. If you use fake-ip, fake-ip-filter must list any domain that genuinely needs a real IP — LAN device management pages, NTP time sync, some game matchmaking services — otherwise those will get a fake address and simply stop working.
What Each of the Three Server Groups Does
default-nameserver has exactly one job: resolving the hostnames of the DoH/DoT servers listed in nameserver itself, so it must contain plain IPs only, never a domain — otherwise you get a chicken-and-egg deadlock. nameserver is the primary resolver group and handles all everyday lookups; encrypted DNS addresses are recommended here. Some cores also support a fallback group: when the primary group's result looks tampered with, the core switches to this group's result instead (typically pointed at overseas resolvers) — an optional extra layer.
dns:
enable: true
listen: 0.0.0.0:1053
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
fake-ip-filter:
- "*.lan"
- "+.local"
- "time.windows.com"
default-nameserver:
- 223.5.5.5
nameserver:
- https://doh.pub/dns-query
- https://dns.alidns.com/dns-query
ping-ing any domain from a terminal returns an address in the 198.18.x.x range — that's by design, not a bug. If you need the real address for a specific domain, just add it to fake-ip-filter.
Plate B-05Proxy Node Fields: the proxies Array
proxies is the array of node definitions. If you use a subscription, this section is auto-generated by the subscription conversion service, and you'll rarely need to hand-write it — the typical case for editing it manually is a self-hosted server or debugging a single node. All protocols share four common fields: name (the node's name, unique across the file, referenced by proxy groups), type (protocol), server (server address — domain or IP), and port (server port). Everything else is protocol-specific; the common ones covered on the downloads page are listed below.
Shadowsocks(type: ss)
Two fields matter most: cipher (encryption method) and password, both of which must match the server exactly — any mismatch shows up as the connection dropping right after it's established, or simply not responding:
- name: "ss-example"
type: ss
server: example.com
port: 8388
cipher: aes-128-gcm
password: "your-password"
VMess(type: vmess)
Identity here is the uuid, which must match the server character-for-character; alterId should be 0 on any current deployment; cipher is usually auto. Nodes running over WebSocket + TLS also need network: ws and a ws-opts sub-section, where path and headers.Host must line up exactly with the server config — getting either wrong is one of the most common causes of a VMess node timing out:
- name: "vmess-example"
type: vmess
server: example.com
port: 443
uuid: 00000000-0000-0000-0000-000000000000
alterId: 0
cipher: auto
tls: true
network: ws
ws-opts:
path: /your-path
headers:
Host: example.com
Trojan and Hysteria2
Trojan authenticates with password and relies on a real TLS certificate, so sni needs to point at the domain the certificate was issued for. Hysteria2 runs over QUIC (UDP transport); besides password, you can declare up / down bandwidth to enable its congestion control — if your network blocks UDP, this protocol simply won't work at all, so that's the first thing to rule out when troubleshooting:
- name: "trojan-example"
type: trojan
server: example.com
port: 443
password: "your-password"
sni: example.com
- name: "hy2-example"
type: hysteria2
server: example.com
port: 443
password: "your-password"
skip-cert-verify: true disables certificate verification on the server, which removes any protection against impersonation — use it only as a temporary crutch when self-hosting and debugging, never leave it on long-term just because it "makes the connection work." A connection that only works with this on almost always points to a wrong sni or a certificate misconfiguration.
If the fields all check out and it still won't connect, the problem usually isn't the config file itself — work through the node timeout checklist and check subscription validity, system time, and firewall settings in that order.
Plate B-06Proxy Group Fields: the proxy-groups Array
Proxy groups sit between rules and nodes: a rule points at a group name, and the group decides which actual node handles the traffic. The value of this extra layer is that when a subscription renames or adds/removes nodes, you only need to update group membership (subscription conversion usually handles this automatically) — not a single rule line has to change. The four common group types:
| type | Behavior | Typical Use |
|---|---|---|
select | Manually pick a member; the selection sticks until changed again | The main outbound group, selected by hand in the client panel |
url-test | Periodically pings members and auto-picks the lowest-latency one | An auto-select group, itself used as a member of a select group |
fallback | Uses the first available member in list order | Primary/backup failover: falls through when the preferred node fails |
load-balance | Spreads connections across multiple members | Load-balance traffic across several nodes |
Auto-Test Parameters
url-test and fallback rely on three parameters: url should be a lightweight test endpoint (commonly https://www.gstatic.com/generate_204, which returns an empty response and costs almost nothing); interval is the test cycle in seconds, commonly 300; tolerance is in milliseconds and means a new node's latency has to beat the current one by at least that much before switching — this stops flapping between two nodes with near-identical latency. The optional lazy: true makes a group only run its speed test when it's actually in use, cutting down on background requests.
Nesting Groups and Built-In Outbounds
Groups can nest: a common pattern is to build one select group as the main outbound, list a url-test auto group alongside several manual nodes as its members, run on the auto group day to day, and flip to a specific node with one click when needed. DIRECT and REJECT are two built-in outbounds provided by the core — they can be used directly as a member of any group or the outbound of any rule, no definition required:
proxy-groups:
- name: "Proxy Select"
type: select
proxies:
- "Auto Select"
- "HK-01"
- "JP-01"
- DIRECT
- name: "Auto Select"
type: url-test
url: https://www.gstatic.com/generate_204
interval: 300
tolerance: 50
proxies:
- "HK-01"
- "JP-01"
A naming rule worth following: group names shouldn't duplicate node names, and any name referenced in a rule or group membership must match its definition character-for-character — including emoji, spaces, and full-width vs. half-width characters. A single mismatch makes the core reject the entire config with a "proxy not found"-style error, which is also the usual cause of a config suddenly breaking after a subscription renames its nodes.
Plate B-07Rule Syntax and Match Order
The rules array is matched top to bottom, stopping at the first match — rules after that point are never even considered. Write order is priority order; that's the one principle everything about routing behavior comes back to. Each rule follows the basic pattern "type,match-value,outbound", and some types accept extra parameters. The outbound can be a proxy group name, a node name, or one of the built-ins DIRECT / REJECT.
| Type | Match Basis | Example |
|---|---|---|
DOMAIN | Exact domain match | DOMAIN,dl.google.com,Proxy Select |
DOMAIN-SUFFIX | Domain suffix (including the domain itself) | DOMAIN-SUFFIX,github.com,Proxy Select |
DOMAIN-KEYWORD | Domain contains a keyword | DOMAIN-KEYWORD,google,Proxy Select |
GEOIP | Geographic origin of the destination IP | GEOIP,CN,DIRECT |
IP-CIDR / IP-CIDR6 | Destination IP falls within a subnet | IP-CIDR,192.168.0.0/16,DIRECT,no-resolve |
DST-PORT | Destination port | DST-PORT,22,DIRECT |
PROCESS-NAME | Name of the process making the connection (desktop only) | PROCESS-NAME,Steam.exe,DIRECT |
RULE-SET | Matches an external rule set (see B-08) | RULE-SET,reject-list,REJECT |
MATCH | Matches unconditionally — the catch-all | MATCH,Proxy Select |
Ordering Principles and no-resolve
Recommended order: exact matches (DOMAIN) first, range-based matches (DOMAIN-SUFFIX / DOMAIN-KEYWORD) in the middle, IP-based matches (GEOIP / IP-CIDR) toward the end, and MATCH must be the very last line and nothing else. IP-based rules need a domain resolved to an IP before they can match, which adds resolution overhead — for rules targeting things that are already IPs, like private or reserved ranges, append no-resolve at the end of the line to declare "skip this rule if the target isn't already an IP" and avoid pointless resolution:
rules:
- DOMAIN,dl.google.com,Proxy Select
- DOMAIN-SUFFIX,github.com,Proxy Select
- DOMAIN-KEYWORD,google,Proxy Select
- DOMAIN-SUFFIX,cn,DIRECT
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
- GEOIP,CN,DIRECT
- MATCH,Proxy Select
Three Common Mistakes
First: writing rules after MATCH — since matching stops at the first hit, anything after it never runs and is dead weight. Second: assuming DOMAIN-SUFFIX,cn covers every site inside mainland China — it only matches the .cn suffix, and plenty of services there use .com domains; the catch-all for that traffic should be GEOIP,CN. Third: an outbound name that doesn't exactly match its proxy group definition (off by one space or an emoji), which makes the entire config refuse to load. For the full write-up on domestic vs. overseas routing and how matching plays out, see rule-based routing in practice; if you just want something that works right now, copy the ready-made rule block from the setup guide.
log-level to debug, visit the target site, and search the log for that domain — you'll see exactly which rule matched and which outbound it took. Far faster than guessing.
Plate B-08Rule Providers: External Rules via rule-providers
Hand-maintaining thousands of lines of rules isn't realistic. Rule providers move bulk rules out into standalone files — the config only keeps a source declaration and a one-line reference, and the core pulls updates on a schedule. Two pieces work together: the top-level key rule-providers defines the source, and a RULE-SET entry in rules consumes it.
Field-by-Field
type is either http (fetched remotely) or file (a local file). behavior declares what shape the file's contents take, one of three: domain means the file is just a list of domains; ipcidr means it's just a list of subnets; classical means every line is a full "type,match-value" rule. behavior must match the file's actual content — a mismatch doesn't throw an error, it fails silently, which is the number one reason a rule provider "looks loaded but does nothing." format declares the file format (yaml or text); url is the remote address; path is the local cache path — don't let two rule providers share the same path, or they'll overwrite each other; interval is the refresh cycle in seconds, so 86400 means once a day.
rule-providers:
reject-list:
type: http
behavior: domain
format: yaml
url: https://example.com/rules/reject.yaml
path: ./ruleset/reject.yaml
interval: 86400
cn-cidr:
type: http
behavior: ipcidr
format: yaml
url: https://example.com/rules/cn-cidr.yaml
path: ./ruleset/cn-cidr.yaml
interval: 86400
rules:
- RULE-SET,reject-list,REJECT
- RULE-SET,cn-cidr,DIRECT,no-resolve
- GEOIP,CN,DIRECT
- MATCH,Proxy Select
Two more things: a RULE-SET entry's position inside rules still follows the overall "top to bottom, stop at first match" order — rule providers don't jump the queue. When referencing an ipcidr-type provider, the same logic as IP-CIDR applies, so appending no-resolve to that reference line is a good idea. Remote rule providers need network access on first fetch — if you're not yet connected during initial setup, point type: file at a local file bundled with the client as a stopgap.
Plate B-09Override and Merge: Making Manual Edits Survive a Subscription Refresh
The Problem and the Rule
Editing the config file a subscription generated is the most common trap for newcomers: the next subscription update regenerates the whole file from scratch, and every manual edit disappears with it. The correct approach follows one rule — never touch the subscription's raw output; put every change in an "override layer" instead. The client merges the override with the subscription's raw content on every load, before handing it to the core, so no matter how the subscription updates, your customizations stick around.
Where Each Client Puts the Override
Every major client offers this mechanism, under different names but the same idea: Clash Verge Rev calls it "Global Extended Config," split into Merge (declarative) and Script (scripted rewriting); Clash Plus has a config override entry; FlClash supports config overlays. Exact menu locations and field names shift as clients evolve, so defer to what's documented inside the client itself. Here's a Merge-style example that inserts two custom rules at the very top of the subscription's rules and replaces the entire DNS section:
prepend-rules:
- DOMAIN-SUFFIX,intra.example.com,DIRECT
- PROCESS-NAME,Steam.exe,DIRECT
dns:
enable: true
enhanced-mode: fake-ip
A prepend- prefix means "insert before the subscription's array of the same name" (highest priority); append- means "add to the end." Writing a plain top-level key (like dns in the example above) replaces that entire section from the subscription outright. For custom rules, prepend is the right call almost every time — since matching stops at the first hit, only rules placed at the very top are guaranteed to take effect first.
Checking Your Work After a Change
Every time you edit the override, follow the same checklist: save the override → reload the config in the client → confirm the log shows no load errors → run the command below to verify the proxy port is actually forwarding traffic → visit the target site once in the connections panel and confirm it matched the rule and outbound you expected. The command returning HTTP/2 204 or 204 No Content means the link is working:
curl -x http://127.0.0.1:7890 https://www.gstatic.com/generate_204 -I
Plate B-10Load Error Reference: From Error Message to the Exact Line
After a config gets edited wrong, the core refuses to load it and leaves a message in the log. The exact wording varies slightly between clients, but the categories are fixed — once you recognize the category, you can go straight to the field at fault without reading the whole file line by line. The table below maps the most common messages to their real causes; sort by message first, then go back to the matching plate to check your syntax.
| Message Keyword | Real Cause | What To Do |
|---|---|---|
yaml: line N / did not find expected key | Indentation is inconsistent, or a Tab got mixed in | Jump to around line N, replace every Tab with two spaces, and check that dashes line up |
could not find expected ':' | Missing space after a colon, or a value with special characters wasn't quoted | Add the missing space after the colon; wrap any value containing #, a colon, or curly braces in double quotes |
proxy not found / proxy 'X' not found | A proxy group or rule references a node/group name that doesn't exist | Compare the definition and the reference character by character — watch for spaces, emoji, and full-width vs. half-width differences |
rules[N] error / unsupported rule type | A rule type is misspelled, or has the wrong number of parameters | Go back to the type table in Plate B-07 and check spelling; confirm all three parts of "type,match-value,outbound" are present |
address already in use | The port is already in use by another process (often a previous core that didn't fully quit) | Kill the leftover process, or change mixed-port to a different port and update the system proxy to match |
DNS init failed / dns error | A domain name was put in default-nameserver instead of a plain IP | Switch it to a plain IP like 223.5.5.5 — see Plate B-04 for details |
A General-Purpose Binary Search Approach
If the error only gives you a vague line number and that line looks fine, the real cause is usually a structure above it that never closed properly, pushing the error down onto the next line. Binary search is the fastest way through this: comment out the entire rules section and reload — if it loads, the problem is in the rules. If not, comment out proxy-groups too, and keep narrowing upward section by section until you've pinned down the block, then halve the block itself the same way. YAML comments start with # at the beginning of a line, and any text editor can comment out a block in one action — a few rounds of this will converge on the exact lines at fault.
There's another category that's even more time-consuming: no error, but the wrong behavior. The core loads cleanly, the log is spotless, but things still don't work as expected. The root cause here usually isn't syntax but semantics — an earlier, broader rule is matching first and shadowing the one you meant to use, a rule provider's behavior doesn't match its actual file content, or the override layer failed to parse and silently fell back. The fix is the same either way: set log-level to debug, visit the target site once, and check which rule and outbound the connection actually matched in the log — that turns guessing into seeing. If the log doesn't even show that domain, the traffic isn't reaching the core at all, and you should go back to the system proxy troubleshooting guide to fix the entry point first.
Plate B-11Backup, Versioning, and Long-Term Maintenance Habits
What Actually Needs Backing Up
Only three things really need to be kept long-term: the subscription link itself (lost it? go grab it again from your provider's dashboard); the override layer you wrote (the one piece of manual work that can't be automatically regenerated); and any local files backing custom rule providers (if you used type: file). The generated config.yaml itself doesn't need backing up — it can be re-fetched at any time, and backing it up tends to cause the confusing scenario of "I edited the backup file and can't figure out why nothing changed." Keep those three things in one plain-text folder, and reinstalling on a new machine takes minutes: install the client → paste in the subscription → paste back the override layer, and you're fully restored.
Use Comments Instead of Memory
Every time you add a custom rule to the override layer, add one line above it explaining why it's there and when it's safe to remove. Six months later, an uncommented rule is one you're too afraid to delete and can't remember the purpose of, so it just stays forever; a commented one can be cleaned up on schedule. The cost is one line of text; the payoff is a config that doesn't slowly calcify into unmaintainable legacy cruft:
prepend-rules:
# Company intranet OA, must stay direct — safe to remove after I leave
- DOMAIN-SUFFIX,intra.example.com,DIRECT
# Steam downloads go direct to use full bandwidth, added 2026-07
- PROCESS-NAME,Steam.exe,DIRECT
Three Things Worth Checking Periodically
First: a rule provider's interval and path — sharing one path across multiple providers makes them overwrite each other, an easy copy-paste mistake when adding a new one, so double-check after every addition. Second: proxy group membership — when a subscription renames its nodes, node names hand-written into the override layer can go stale, showing up as a group missing members or a proxy not found load error. Third: core and client versions — newer fields get flagged as unrecognized on older cores, so it's safer to update the client first before turning on a new field; see the client comparison page for update cadence across clients, and the downloads page for installers.
When To Rebuild Instead of Keep Patching
Once a config has been hand-edited so many times you can no longer tell which fields are actually doing anything, rebuilding is faster than continuing to patch: delete the old config, re-fetch a clean copy from the subscription, pick out only the rules from the override layer you're sure you still need and paste those back in, and drop everything else. Use the minimal skeleton from Plate B-02 as your baseline — five parts and it runs — then add sections back one at a time, reloading after each one, so any mistake is caught immediately. It feels like the slower path, but it's far faster than debugging a file that's already a mess.
Plate B-12Field Combinations for Common Scenarios
This section combines fields from earlier plates into a handful of complete, ready-to-use recipes covering the four most common everyday needs. Each one belongs in the override layer, so subscription updates never touch it — copy it in and swap in your own domains or process names where noted in the comments.
Scenario 1: A Site That Must Always Go Direct
Company intranets, banking sites, and government portals that only recognize connections from within mainland China will reject or repeatedly re-verify traffic coming through a proxy. Point DOMAIN-SUFFIX at DIRECT, and put it in the prepend section so it takes priority over anything the subscription defines:
prepend-rules:
- DOMAIN-SUFFIX,intra.example.com,DIRECT
- DOMAIN-SUFFIX,example-bank.com,DIRECT
Scenario 2: Routing an Entire App Direct or Through the Proxy
On desktop, you can route by process name — useful for game launchers or large file sync tools that don't need a proxy but will happily saturate your bandwidth. Use the actual executable name as the system sees it: Windows includes the .exe extension, macOS and Linux usually don't; you can see the exact name right in the client's connections panel:
prepend-rules:
- PROCESS-NAME,Steam.exe,DIRECT
- PROCESS-NAME,Docker Desktop.exe,Proxy Select
Scenario 3: Blocking Ads and Tracking Domains
Point domains you don't want connections to at REJECT, and the core will refuse the connection outright. A handful of domains can be hand-written; anything at scale should go through a rule provider instead, auto-updated via rule-providers, rather than hand-maintaining thousands of lines:
prepend-rules:
- DOMAIN-KEYWORD,doubleclick,REJECT
- DOMAIN-SUFFIX,ads.example.com,REJECT
Scenario 4: Region-Specific Exits
Streaming services, AI tools, and other region-locked sites need traffic pinned to a node in a specific region. The pattern is to build a select or url-test group containing only nodes from that region, then point the relevant domains at that group — so adding or removing nodes only touches group membership, never the rules:
prepend-proxy-groups:
- name: "Japan Exit"
type: url-test
url: https://www.gstatic.com/generate_204
interval: 300
proxies:
- "JP-01"
- "JP-02"
prepend-rules:
- DOMAIN-SUFFIX,example-stream.jp,Japan Exit
All four recipes follow the same pattern: decide "which traffic" (domain, process, IP range), decide "which outbound" (a built-in or a proxy group), and put it in the prepend section to guarantee priority. Once you've internalized these three steps, you can build any new rule yourself without waiting for a ready-made config. For more on how rules interact and more advanced domestic/overseas routing setups, see rule-based routing in practice.
That covers the main fields in config.yaml. For a specific symptom not covered here, search the FAQ page by category; if you're unsure which client should run this config, check the client comparison page's recommendations; and if you haven't installed a client yet, grab one for your platform on the downloads page — Clash Plus is the top pick across platforms.