Clash config.yaml Structure Explained Section by Section: Ports to Rules
A top-to-bottom walkthrough of config.yaml: ports and mode, dns, proxies, proxy-groups, rules, plus common indentation and field-name pitfalls.
The core config file for both Clash and Clash Meta (mihomo) is a single YAML document, conventionally named config.yaml. It's made up of several top-level sections, each handling one concern — ports and run mode, DNS resolution, the proxy node list, proxy groups, and routing rules. Understanding how these sections relate to each other and what each field means is more useful than memorizing a "universal config," because a remote config from a subscription often needs manual review or merging with local settings. If you don't understand the structure, it's easy to break the whole file with a single wrong indent. This article walks through the file top to bottom, section by section, and lists the real-world pitfalls in each one.
General settings: ports, mode, and LAN access
The top of the file is usually a flat block of key-value pairs that define the client's own behavior, unrelated to any specific node. Common fields:
port: 7890
socks-port: 7891
redir-port: 7892
allow-lan: false
mode: rule
log-level: info
external-controller: 127.0.0.1:9090
port is the HTTP proxy port and socks-port is the SOCKS5 proxy port; they're independent, so you can enable both or only one. redir-port is for transparent proxy setups, which regular desktop users rarely need. allow-lan controls whether other devices on the local network can use this machine's IP to proxy through it — set it to true when sharing the connection with a phone or router, and make sure your firewall allows the relevant ports.
mode has three values: rule routes traffic according to the rules section, global forces all traffic through a single selected node (useful for testing whether a node itself works), and direct sends everything straight through with no proxy. Keep rule for daily use, and only switch to global temporarily to test node connectivity — remember to switch back afterward, or you'll end up puzzled why "the rules are configured but everything is going through the proxy anyway." Once external-controller is enabled, the client exposes a local API port for dashboards or third-party tools to read status from; leave it blank or remove the line if you don't need that.
The dns section: resolution method decides routing accuracy
The dns section controls domain resolution, and getting it wrong directly hurts routing accuracy, especially with TUN mode enabled. A typical example:
dns:
enable: true
ipv6: false
default-nameserver:
- 223.5.5.5
- 119.29.29.29
nameserver:
- https://doh.pub/dns-query
- tls://dns.rubyfish.cn:853
fallback:
- https://1.1.1.1/dns-query
fake-ip-range: 198.18.0.1/16
fake-ip-filter:
- "*.lan"
- localhost.ptlogin2.qq.com
default-nameserver is the server used to resolve the domain names of the DoH/DoT addresses listed under nameserver below — it must be a plain IP, never a domain name, or you'll hit a circular dependency ("resolving the resolver's own domain") that causes a startup error. nameserver is the actual list of resolvers used for real lookups, while fallback is a backup list used when the nameserver result may be tampered with; it's often paired with fallback-filter to judge by geography or the returned IP range.
fake-ip-range works together with TUN mode: the client assigns each domain a placeholder IP from this range, then restores the real domain name at the traffic layer to match against rules — this lets domain-based rules still work correctly under global proxy mode. Domains listed under fake-ip-filter (such as internal domains or LAN device names) are excluded from fake-IP assignment and use real DNS resolution instead; getting this entry wrong often shows up as LAN devices (NAS, printers, router admin pages) suddenly becoming unreachable.
fake-ip affects things, it's safest to leave the dns section at its defaults or keep whatever your subscription provides — don't trim fields casually. DNS misconfiguration tends to show up as "some sites load, others don't," which is annoying to debug.The proxies section: node list field conventions
proxies is an array where each element describes one node, and the fields differ by protocol type. Here's an example using the common ss (Shadowsocks) and vmess types:
proxies:
- name: "HK-01"
type: ss
server: hk01.example-node.invalid
port: 8443
cipher: aes-256-gcm
password: "your-password-here"
- name: "SG-02"
type: vmess
server: sg02.example-node.invalid
port: 443
uuid: 00000000-0000-0000-0000-000000000000
alterId: 0
cipher: auto
tls: true
network: ws
ws-opts:
path: /ws
A few common mistakes: name is the human-readable label that proxy-groups use for exact-match lookups — if you rename a node without updating references to it in proxy-groups, that node "disappears" from the group; type determines how the rest of the entry is parsed, and a typo (such as writing vmes instead of vmess) makes the client silently skip that node entirely, usually without any error — the node list just quietly has one fewer entry, which is easy to overlook; password and uuid should be wrapped in quotes, especially when the password contains YAML special characters like : or # — without quotes, YAML may parse it into a different structure and break that entire node entry.
If nodes come from an imported subscription link, the proxies section usually doesn't need manual editing — the client fills it in automatically. You'd only edit this section directly when self-hosting a node or adding one by hand.
The proxy-groups section: organizing nodes into selectable groups
proxy-groups determines what options appear and can be switched between in the client UI, and which routing exit the rules section ultimately references. The three most common types:
proxy-groups:
- name: "Auto"
type: url-test
proxies:
- HK-01
- SG-02
url: "http://www.gstatic.com/generate_204"
interval: 300
- name: "Fallback"
type: fallback
proxies:
- HK-01
- SG-02
url: "http://www.gstatic.com/generate_204"
interval: 300
- name: "Select Node"
type: select
proxies:
- Auto
- HK-01
- SG-02
- DIRECT
url-test automatically picks the fastest node by latency, fallback uses the first available node in list order, and select is manual selection that lets the user pick from the client UI. Note that a select group's proxies list can directly reference the name of another proxy group (as with "Auto" above) — this is a common nested-group pattern that lets you wrap "auto speed test" as one of the options inside "Select Node." Names in a proxies list must exactly match either a name in the proxies section or the name of another proxy group, including case and full-width/half-width characters — a single mismatched character causes that group to fail on startup, or the option to be silently dropped.
interval is the speed-test interval in seconds; setting it too low increases test frequency and wastes extra bandwidth and battery. For url, use a dedicated connectivity-check address rather than a regular website URL, or the test results may be skewed by that site's own response time.
The rules section: match order and syntax
rules is the last section in the file, matched line by line from top to bottom, stopping at the first match — an incorrect order is the most common issue and also the hardest to spot on your own. Basic syntax:
rules:
- DOMAIN-SUFFIX,google.com,Select Node
- DOMAIN-KEYWORD,github,Select Node
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
- GEOIP,CN,DIRECT
- MATCH,Select Node
Each line consists of three parts: match type, match content, and target group, with some types taking an optional fourth parameter (such as no-resolve, meaning skip DNS resolution for that rule — commonly used on IP-range rules for efficiency). DOMAIN-SUFFIX matches a domain and its subdomains, DOMAIN-KEYWORD matches a keyword contained anywhere in the domain, IP-CIDR matches an IP range, GEOIP matches the country/region an IP belongs to, and MATCH is the catch-all rule that must be the very last line of the rules section, meaning "anything not matched above goes here."
Because matching is sequential, placing a broad rule (such as a GEOIP,CN,DIRECT) before specific rules means the specific domain rules after it will never trigger — this shows up as "I configured a site to use the proxy but it's not actually working." The correct order is: specific domain/app rules first, then broader region/IP-range rules, and finally MATCH at the very end.
Common indentation errors and field-name pitfalls
YAML is extremely sensitive to indentation, and the following mistakes keep showing up in real usage:
- Mixing spaces and tabs: YAML doesn't allow tab indentation. If your text editor silently converts indentation to tabs, the client will throw a syntax error on startup — use an editor that can insert spaces in place of tabs.
- Inconsistent indentation within the same array element: fields belonging to the same array element must line up exactly in the same column; even one extra space gets parsed as a nested level, causing that field to be ignored or the whole node entry to fail parsing.
- Unquoted strings causing type misinterpretation: if a password or UUID contains characters YAML treats specially (colons, hash signs, brackets), it must be quoted, or the parsed result won't match what you intended.
- Misspelled field names that don't error out: most clients silently ignore unknown fields instead of raising an error — for example, typing
allow_laninstead ofallow-lanstill loads fine, but that setting quietly stops working, and it's easy to overlook during troubleshooting. After editing a key field, recheck the client logs or compare against an official sample config. - proxy-groups referencing a node name that no longer exists: renaming or deleting a node without updating references leaves a group missing an option, or causes it to fail loading entirely — after editing the proxies section, do a full-text search to confirm every reference to that node name in proxy-groups is updated too.
Going through the config file with these sections and pitfalls in mind should cover most "the subscription imports fine but rules don't work" or "I changed the port but nothing happened" issues. If the client won't start after a manual edit, check the indentation of whatever you last changed, then confirm field names match the official docs — that covers the vast majority of cases.