1. What This Page Covers & the YAML Structure
Clash-family clients — and mihomo, the core that actually runs underneath — read a YAML config file to determine every bit of behavior. A subscription link is, at its core, a config file hosted on a server that you can re-fetch anytime. Every toggle you see in a client's UI ultimately maps to a field in this same file. Once you understand its structure, you understand the relationship between your subscription content, the client's settings, and the core's logs all at once — no more clicking through menus at random when something breaks.
This page splits the work with the getting started guide along the lines of "quick setup" vs. "deep reference": the guide walks you through one complete run from importing a subscription to confirming you're online, while this page systematically answers "what does this field mean, and what else can I put here?" outside of that flow. Treat the two as complements — when the guide glosses over a field, jump to the matching section here using the table of contents above.
Top-Level Structure: Five Main Blocks
A complete config file breaks down into roughly five blocks. Their order in the file doesn't affect how it's parsed, but almost every subscription follows the order below — useful to know when you're skimming someone else's config and need to find something fast:
| Block | Purpose | Typical Fields |
|---|---|---|
| General fields | Global behavior: ports, running mode, logging, LAN sharing, and more | mixed-port、mode、log-level |
dns | How the core handles domain resolution, including Fake-IP | enhanced-mode、nameserver |
proxies | Lists every available proxy node and its protocol parameters | type、server、port |
proxy-groups | Organizes nodes into selectable, speed-testable proxy groups | type: select、url-test |
rules | Matches traffic in order to decide which proxy group handles it | DOMAIN-SUFFIX、GEOIP、MATCH |
Three Hard Rules of YAML Syntax
YAML is extremely sensitive to formatting, and nine out of ten config errors are syntax problems, not field problems. Remember three rules before you start editing: first, indentation must use spaces only — a single Tab character is enough to break parsing for the whole file, and the reported error line often points somewhere further down, making it easy to miss; second, a colon must be followed by a space, so port:7890 is invalid while port: 7890 is correct; third, if a node name contains emoji, a hash symbol, quotes, or starts with a special character, wrap the whole string in double quotes, or it may get parsed as a comment or syntax token. A # at the start of a line marks a comment — when debugging, prefixing a rule with a hash to disable it temporarily is safer than deleting it outright. If you're unsure about a term, check the "Subscriptions & Configuration" category in the glossary.
2. General Fields: Ports, Modes & Runtime Options
General fields sit at the top level of the file, outside any indented block, and determine how the core behaves overall. Here's a minimal working config you can use as a starting point:
mixed-port: 7890
allow-lan: false
mode: rule
log-level: info
ipv6: false
external-controller: 127.0.0.1:9090
The Port Family: mixed-port and Friends
mixed-port is the most commonly used port field today — it accepts both HTTP and SOCKS5 inbound connections on a single port, so you can point your system proxy at it without separate settings. The older port (HTTP only) and socks-port (SOCKS5 only) fields still work and are handy when you need to hand each protocol to a different app. There's also redir-port and tproxy-port, meant for transparent proxying on Linux and router setups — regular desktop users can skip these entirely. Stick to port numbers above 1024, the reserved system range. If the log shows bind: address already in use on startup, another process (often a leftover proxy instance) is already using that port — pick a different one or close the conflicting process.
mode: Three Running Modes
| Value | Behavior | When to Use |
|---|---|---|
rule | Every connection is matched against the rules block, in order, to decide routing | Everyday default — balances speed and accuracy |
global | Ignores all rules; every connection uses one globally selected proxy | Quickly testing whether a specific node works |
direct | All traffic connects directly, bypassing every node | Figuring out whether an issue is proxy-related at all |
The Rule/Global/Direct toggle in a client's UI is exactly this field. When troubleshooting, switch to direct first to confirm your local network is fine, then global to confirm a node works, and finally back to rule to check your rules — this three-step process is the fastest way to isolate a problem.
Other Frequently Used Fields
Setting allow-lan to true lets other devices on the LAN point their gateway at this machine's proxy port — handy for sharing the proxy with a smart TV or game console. When enabled, it's worth pairing it with bind-address to limit which network interface is listening, so you're not exposing the port to strangers on a public network. log-level ranges from most to least verbose: debug, info, warning, error, silent. Bump it to debug temporarily to see exactly which rule each connection matched. external-controller opens a RESTful API for dashboard-style tools to read status and switch nodes; if a secret field is set, requests must include that same key — always use a dummy value like secret: "xxxx" in examples. ipv6 is off by default, and turning it on in a network with incomplete IPv6 support tends to introduce resolution timeouts rather than fix anything.
3. The DNS Block: Resolution Modes & Fake-IP
A lot of "my rule is definitely right but isn't working" or "some sites work, some don't" problems trace back to DNS. The reason the core ships its own DNS setup is that routing rules rely heavily on domain names and geolocation — if resolution gets tampered with or bypasses the core entirely, everything downstream is matching against the wrong answer. Here's a common DNS block:
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"
nameserver:
- https://223.5.5.5/dns-query
fallback:
- https://1.1.1.1/dns-query
fallback-filter:
geoip: true
geoip-code: CN
enhanced-mode: fake-ip vs. redir-host
In fake-ip mode, the core doesn't actually resolve a domain query — it hands back a placeholder address from the reserved range set in fake-ip-range (default 198.18.0.1/16) and remembers which domain that placeholder maps to. When an app later connects to that placeholder, the core looks up the real domain from its mapping and runs rule matching against it. The upside: no resolution delay, and domain-based rules match almost perfectly. The downside: anything that depends on a real IP — LAN device discovery, some game matchmaking, verification services that need to see your actual IP — will break because it only ever sees the placeholder. That's exactly what fake-ip-filter is for: domains listed there skip the placeholder logic entirely and get a real resolved address. redir-host mode always returns the real IP, so it's more compatible but takes a longer path to resolve. For a full comparison of how both modes actually work, see the blog post "What Is Fake-IP Mode".
nameserver vs. fallback
nameserver is your default upstream — usually a geographically close, fast-responding server. fallback is the backup, used when fallback-filter flags the default result as suspicious (for example, when geoip: true is set and a domain outside your country resolves to a local IP anyway). Upstream addresses support four formats: plain IP (UDP 53), tls:// (DoT), https:// (DoH), and quic:// (DoQ) — the encrypted formats help prevent resolution requests from being tampered with in transit. Make sure at least one entry in nameserver can be resolved without going through the proxy, or you'll hit a chicken-and-egg problem: resolving the proxy server's own domain also needs DNS.
enhanced-mode or changing fake-ip-range, your system and browser may still be holding onto old resolution results — which can look like the change broke your connection entirely. Restart the client and flush your system's DNS cache (on Windows, run ipconfig /flushdns) before checking again.4. Proxy Node Fields: proxies Explained
proxies is a list where each entry describes one node. In everyday use with a subscription, this section is generated by the subscription server and you'll rarely write it by hand — but understanding it pays off in two ways: you can diagnose connection issues at the field level (wrong port, missing transport parameters, mismatched encryption), and you can add a self-hosted node temporarily without needing a conversion tool. Every protocol shares four base fields: name (a unique identifier used when referencing this node elsewhere — duplicates will break parsing), type (protocol), server, and port. udp: true tells the node to also forward UDP traffic, which voice calls and games rely on.
Field Shapes for Three Common Protocols
proxies:
- name: "Example-SS"
type: ss
server: node1.example.com
port: 8388
cipher: aes-128-gcm
password: "your-password"
udp: true
- name: "Example-VMess"
type: vmess
server: node2.example.com
port: 443
uuid: 00000000-0000-0000-0000-000000000000
alterId: 0
cipher: auto
tls: true
network: ws
ws-opts:
path: /ws
headers:
Host: node2.example.com
- name: "Example-Trojan"
type: trojan
server: node3.example.com
port: 443
password: "your-password"
sni: node3.example.com
For Shadowsocks (ss), cipher and password must match the server exactly — get the encryption method one letter off and the connection just fails silently. VMess uses uuid as its credential, and alterId is fixed at 0 under the current protocol version. network determines the transport layer: set it to ws and you'll need matching ws-opts specifying the path and Host header, or grpc with matching grpc-opts. Trojan always runs over TLS, so if sni doesn't match the certificate's domain, the handshake gets rejected outright — for test environments with self-signed certs you can add skip-cert-verify: true, though it's not recommended for real use.
mihomo Protocol Extensions
As the leading core in use today, mihomo supports newer protocols beyond the classics above — VLESS, Hysteria2, TUIC — each with its own field set (Hysteria2's up/down bandwidth declarations, for instance). Whether these work depends on the core version bundled with your client, which is one reason the client comparison page recommends prioritizing mihomo-based clients (like Clash Plus for every platform, or Clash Verge Rev on desktop): older cores just throw "unsupport proxy type" and refuse to load the entire config when they hit a node using a newer protocol.
5. Proxy Group Fields: The Four Types of proxy-groups
If proxies is the raw material, proxy-groups is the layer that organizes it into decision units: rules don't point directly at a node, they point at a group, and the group's own logic — manual selection or automatic latency testing — decides the actual exit node. This design means switching nodes never requires touching a single rule. Here's how the four group types behave:
| type | How It Decides | Key Fields |
|---|---|---|
select | You pick manually from the client UI | proxies |
url-test | Tests latency on a schedule and auto-picks the fastest member | url、interval、tolerance |
fallback | Uses the first available member in list order | url、interval |
load-balance | Spreads connections across multiple members | strategy |
proxy-groups:
- name: "Select Node"
type: select
proxies:
- Auto Speed Test
- Example-SS
- Example-VMess
- DIRECT
- name: "Auto Speed Test"
type: url-test
url: http://cp.example.com/generate_204
interval: 300
tolerance: 50
lazy: true
proxies:
- Example-SS
- Example-VMess
Field Details & Nesting Tricks
For auto-testing groups, url should point to a lightweight endpoint that returns a 204 status (every client ships with a default test URL, so you can usually leave it blank). interval sets the retest cycle in seconds — 300 is a common value. tolerance is the switching threshold: if the latency difference between the current and a faster node is smaller than this value in milliseconds, the group won't switch, which prevents two similarly-fast nodes from flip-flopping and constantly resetting your connection. lazy: true skips speed testing while the group isn't in active use, which noticeably cuts background traffic when you have a lot of nodes. A group's members can be node names, other group names, or the built-in policies DIRECT and REJECT — which means you can nest a manual group around an auto group: leave "Auto Speed Test" running day-to-day, and manually pin a specific node when you need to. The one thing to avoid is two groups referencing each other in a loop — the core will reject the config at startup if it detects one. Group names must also be globally unique and get referenced verbatim by rules, so if you rename a group, update every rule that points to it.
6. Rule Syntax: How rules Matches Traffic
rules is where routing decisions actually get made. Every time a new connection comes in, the core checks the list from top to bottom — the first match wins, and nothing after it gets evaluated. That means the order you write rules in is the priority itself; shuffle the same rules around and you can get a completely different routing outcome. Every rule follows the same general format, type,match value,policy, where the policy can be a group name, a node name, or the built-in DIRECT/REJECT:
rules:
- DOMAIN,dl.example.com,Select Node
- DOMAIN-SUFFIX,example.org,Select Node
- DOMAIN-KEYWORD,tracker,REJECT
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
- IP-CIDR6,fd00::/8,DIRECT,no-resolve
- GEOSITE,cn,DIRECT
- GEOIP,CN,DIRECT
- RULE-SET,ads,REJECT
- MATCH,Select Node
Rule Type Reference
| Type | Matches Against | Notes |
|---|---|---|
DOMAIN | Exact domain match | Most precise — matches exactly one domain |
DOMAIN-SUFFIX | Domain suffix | Matches the domain and all subdomains — the most commonly used type |
DOMAIN-KEYWORD | Domain contains keyword | Widest reach, easy to over-match — use sparingly |
IP-CIDR / IP-CIDR6 | Destination IP range | See no-resolve below for pairing it correctly |
GEOIP | Destination IP's geolocation | Relies on the GeoIP database |
GEOSITE | Domain category database | Relies on the GeoSite database |
PROCESS-NAME | Name of the process making the connection | Per-app routing on desktop |
RULE-SET | External rule set | References a set defined in rule-providers |
MATCH | Matches unconditionally | The catch-all rule — must always be last |
no-resolve & Ordering Principles
IP-based rules resolve the domain to an IP by default before comparing — but if a rule is really only meant to catch traffic that was already an IP to begin with (a LAN subnet, say), that resolution is a pure waste, and it can slow down every matching connection. Appending no-resolve to the end of the rule tells it to skip straight past when the target is a domain. As for ordering, follow two principles: put precise, low-cost checks first (the DOMAIN family), and anything that needs resolution or a database lookup later (GEOIP); put reject rules as early as possible so ad requests get blocked before they reach heavier matching logic. How accurate GEOSITE and GEOIP are depends entirely on how current your local database is — letting it go stale is a common, quiet cause of misrouted traffic. See "What to Do When Your GeoIP or GeoSite Database Is Out of Date" for update instructions.
log-level to debug, or check the connections panel in your client, which usually labels each active connection with the rule that routed it — far faster than guessing rule by rule.7. Providers & Rule Sets: proxy-providers and rule-providers
Writing hundreds of nodes and tens of thousands of rules directly into your main config makes it bloated and hard to update. Providers pull that content out into external resources that get fetched, cached, and refreshed on their own schedule — your main config just references them, while the actual content lives remotely. This is the standard shape of modern subscriptions: your subscription link is, in effect, a node list in the proxy-providers sense.
proxy-providers: Hosting Your Node Source
proxy-providers:
main:
type: http
url: "https://sub.example.com/subscribe?token=xxxx"
path: ./providers/main.yaml
interval: 3600
health-check:
enable: true
url: http://cp.example.com/generate_204
interval: 600
type: http pulls from a remote source, paired with a url and a local cache path in path; type: file reads straight from a local file, which works well for a hand-maintained list of self-hosted nodes. interval controls how often it re-fetches, in seconds, and health-check has the core periodically test nodes in the list so proxy groups can filter by availability. To let a proxy group pull nodes from a provider, use the use field alongside (or instead of) proxies:
proxy-groups:
- name: "Subscription Nodes"
type: url-test
use:
- main
url: http://cp.example.com/generate_204
interval: 300
rule-providers: Hosting Your Rule Sets
rule-providers:
ads:
type: http
behavior: domain
format: yaml
url: "https://sub.example.com/rules/ads.yaml"
path: ./ruleset/ads.yaml
interval: 86400
The easiest mistake to make with rule sets is a mismatch between behavior and the actual file contents. It takes three values: domain (the file is all domains), ipcidr (all IP ranges), and classical (every line is a full "type,value" rule). Declare a domain list as classical by mistake and it'll load fine — with zero errors — but never match anything, which makes it a nasty one to debug. format supports yaml and text, and mihomo also supports the smaller, faster-loading binary mrs format. Once declared, a set gets pulled into the main rule table with a line like RULE-SET,ads,REJECT — updating the rule set never requires touching your main config again, which is exactly where the long-term value lies. The token=xxxx in the example is a placeholder; use the full link your subscription service actually gives you.
8. Override & Merge: Keeping Subscription Updates From Wiping Out Your Edits
Editing a subscription's config file directly has one fatal flaw: the next time it auto-updates, the remote content overwrites your local file wholesale, wiping out every rule you added and every DNS tweak you made. Override and Merge exist to solve exactly this — they keep "your changes" and "the subscription's content" stored separately, and the client re-applies your changes automatically every time the subscription updates. Before editing anything, check which mechanism your client actually supports:
| Client | Mechanism | Notes |
|---|---|---|
| Clash Plus (recommended) | Override Config | A consistent override entry point across every platform, mounted per subscription |
| Clash Verge Rev | Merge + Script | Declarative YAML merge, with scripting available for more complex logic |
| FlClash | Override Panel | Graphical overrides for common fields (port/DNS/rules) |
| Clash Meta for Android | Config Add-on | Basic field-overriding support on mobile |
Writing a Declarative Merge
Take Clash Verge Rev's Merge feature as an example — the merge file is itself a YAML document. Fields written directly at the top level (like mixed-port) fully replace the matching field in the subscription. Fields prefixed with prepend- or append- insert content at the start or end of the subscription's corresponding list, which is commonly used to insert your own high-priority rules ahead of the subscription's rules:
mixed-port: 7893
prepend-rules:
- DOMAIN-SUFFIX,corp.example.com,DIRECT
- PROCESS-NAME,steam.exe,Select Node
append-proxies:
- name: "Self-Hosted Backup"
type: ss
server: node9.example.com
port: 8388
cipher: aes-128-gcm
password: "your-password"
The "prepend to the top of the rule table" behavior matters a lot here: since the first match wins, your private rules only get a chance to fire if they come before the subscription's rules. How dictionary-type fields (like dns) merge — full replacement vs. key-by-key merging — varies slightly between clients, so always test with a known piece of traffic after editing. The client comparison page has a side-by-side breakdown of where each client's override entry point lives and how it behaves; for the full setup flow on Windows, including service mode, see "Running Clash Verge Rev on Windows".
9. Common Errors & a Config Troubleshooting Checklist
When a config fails to load, the core's log usually names the category of problem outright — read the log before you start editing, and you'll save yourself a lot of guesswork. Here's how to match common errors to their cause: anything starting with yaml: line N is a syntax error — check that line for bad indentation (a stray Tab is the usual suspect) or a missing space after a colon; proxy not found or proxy group ... not found means a group or rule references a name that doesn't exist, usually because a node got renamed without updating its references, or because a name has a stray space or emoji that doesn't quite match; unsupport proxy type means the config uses a protocol your current core doesn't recognize — either update the client or remove that node; bind: address already in use means the port is taken, covered in chapter 2; and if a rule set loads fine but never matches anything, the behavior mismatch from chapter 7 is the first thing to suspect.
A Seven-Step Checklist for Editing Configs Safely
- Back up first: copy your current working config file before touching anything. If an edit breaks something, you can revert instantly — it's the cheapest insurance you'll ever buy.
- Clear the syntax check: reload the config in your client right after saving, and don't move on to checking behavior until it loads successfully. If it fails to load, fix the syntax at the line number the log points to before stacking on more changes.
- Check name consistency: search the whole file for every node and group name you're referencing, and confirm
proxies,proxy-groups, andrulesall spell it identically — including spaces inside quotes. - Verify rule order: confirm
MATCHis the last rule, reject rules are placed early, and your private rules come before the subscription's rules (if using override, make sure it's actually prepending). - Verify DNS behavior: with Fake-IP on, test local services like a LAN printer or screen casting — if they break, add the relevant domain to
fake-ip-filter. - Isolate with the three modes: if something "won't connect," switch to
directfirst to rule out your local network, thenglobalto rule out the node, then back toruleto check your rules — change one variable at a time. - Watch one full update cycle: confirm that after the subscription auto-updates, your overrides still apply and your port/DNS settings haven't reverted to defaults.
If you've gone through the checklist and still can't pin down the issue, go back to the getting started guide and check whether you missed a step in the basic setup. For a general list of things to check right after a fresh install, see "7 Settings to Check Right After Installing Clash". An outdated client version can also amplify all sorts of compatibility issues — updating to the current version from the download page before retesting is one of the highest-value steps in any troubleshooting flow.