Clash Config File YAML Structure Walkthrough: From Port Settings to the Rules Section
A complete Clash config is made up of general settings, DNS, proxies, proxy-groups, and rules sections. This article walks through each section top to bottom, explaining what each field does, its common values, and the indentation mistakes that trip people up.
Basic structure and how the config gets loaded
The core config read by Clash-family clients (the original Clash, Clash Meta, and its fork mihomo) is a plain-text YAML file, usually named config.yaml. Whether it comes from an auto-downloaded subscription link or is imported manually from a local file, the client ultimately parses this text into structured data, then initializes the corresponding modules — ports, proxy nodes, policy groups, rules — in the order they're declared. Understanding how this file is divided into sections is the first step toward diagnosing problems like "rules aren't taking effect" or "a proxy group shows up empty."
By convention, a config file is organized top to bottom into five main sections: general runtime parameters, DNS resolution settings, the proxies list, proxy-groups, and the rules table. There's no strict ordering requirement between sections — the core matches fields by name, not by line number — but nearly every subscription generator and mainstream template follows this order, and this article does too, so you can check it against your own file section by section.
YAML itself relies on indentation rather than curly braces or closing tags to express hierarchy. That means within a given section, sibling fields must use exactly the same number of leading spaces, and tabs can never substitute for spaces. This is where newcomers editing configs by hand trip up most often, and it gets its own section further down.
General fields: ports, mode, and log level
At the very top of the file you'll usually find a set of standalone key-value pairs that don't belong to any nested block:
port: 7890
socks-port: 7891
mixed-port: 7890
allow-lan: true
mode: rule
log-level: info
external-controller: 127.0.0.1:9090
secret: ""
What each field does:
- port / socks-port: declare the HTTP proxy port and the SOCKS5 proxy port respectively; you need these exact numbers when configuring a proxy manually at the OS or app level.
- mixed-port: a single port that handles both HTTP and SOCKS5 on the same listener. Once set, the two fields above can be omitted — most GUI clients rely on this one field exclusively.
- allow-lan: whether other devices on the local network can connect through this machine's proxy ports. Turn it on if a phone or tablet needs to share a computer's proxy.
- mode: the global run mode. Common values are
rule(route by the rules table),global(send everything through one proxy), anddirect(bypass the proxy entirely). Stick withruleday to day; only switch to the other two temporarily for isolating a single variable while troubleshooting. - log-level: how verbose the logs are —
silent,error,warning,info, ordebug. Bumping it todebugtemporarily reveals the full handshake process when a connection fails. - external-controller: the address and port exposed to an external management panel (such as a built-in dashboard). Most GUI clients already have a management interface baked in, so casual users rarely need to touch this.
- secret: the password used to authenticate against that API. Leave it blank to disable auth entirely — fine for local-only use, but worth setting if the API is exposed to the LAN.
TUN mode fields also live in this section:
tun:
enable: true
stack: system
dns-hijack:
- "any:53"
auto-route: true
auto-detect-interface: true
TUN mode has Clash create a virtual network adapter and take over traffic at the system network layer, so you no longer need to configure HTTP/SOCKS proxies app by app. It's typically used for apps or system-level traffic that doesn't support proxy settings at all. stack picks the protocol stack implementation for the virtual adapter — system relies on the OS's built-in capabilities and tends to be more compatible, while gvisor is a userspace implementation that can be more stable on certain platforms. Turning on TUN mode usually requires running the client as administrator or root — it's a separate traffic-interception mechanism entirely independent of the proxy port settings.
The DNS section, field by field
The DNS section governs how domain resolution requests are handled, and it directly affects both routing accuracy and resolution speed. It's also the section most often overlooked and most often the actual source of trouble:
dns:
enable: true
ipv6: false
default-nameserver:
- 223.5.5.5
- 119.29.29.29
nameserver:
- https://dns.alidns.com/dns-query
- tls://dns.google
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: used to resolve the DoH/DoT server hostnames listed under
nameserverandfallbackbelow. It must be plain IP addresses — no further domain resolution layered on top — otherwise you get a circular dependency. - nameserver: the actual servers used to resolve ordinary domains. Supports plain UDP as well as DoH (
https://) and DoT (tls://) prefixes. - fallback: the backup resolver used when a domain is determined by the rules to need routing through the proxy — usually a server outside the region or an encrypted resolution service, to avoid tampered results.
- fake-ip-range: once fake-ip mode is enabled, this is the virtual IP range temporarily assigned to domains. The client gets this virtual IP, and the core later resolves it back to the real destination when the connection is actually established — a bridge between rules that match on domain name and forwarding that ultimately happens at the IP layer.
- fake-ip-filter: domains that should skip fake-ip and get the real IP directly. Common cases include LAN device discovery and live-stream chat features that depend on the genuine IP — leaving these out of the filter breaks that functionality.
A misconfigured DNS section typically shows up like this: the rules table looks completely correct, yet certain sites still exit through the wrong route, or connections succeed but time out constantly. More often than not the rules aren't actually wrong — the resolution step already returned something unexpected before the rules ever get a chance to run. Check the DNS section before you go digging through rules.
proxies and proxy-groups: how nodes and policy groups relate
proxies is a list where each item describes a single proxy node — protocol type, server address, port, encryption method, and other connection parameters. For example:
proxies:
- name: "HK-01"
type: ss
server: example-hk.example.com
port: 8443
cipher: aes-256-gcm
password: "your-password"
- name: "SG-02"
type: vmess
server: example-sg.example.com
port: 443
uuid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
alterId: 0
cipher: auto
Common type values include ss (Shadowsocks), vmess, trojan, socks5, and http. Each protocol requires a slightly different set of fields; missing a required field usually makes that node show up as unavailable, or the client skips loading it entirely. If this section comes from a subscription link, you typically don't write it by hand — the subscription provider generates it. Manual editing is only needed when adding nodes yourself, and that's when you need to double-check every field name matches the protocol.
The proxy-groups section organizes the nodes listed above into named "policy groups" that rules can reference. Rules point to a policy group's name, not to an individual node's name, so switching exit nodes only means adjusting the group internally — the rules table itself never needs to change. Common policy group types:
| type value | Behavior | Typical use |
|---|---|---|
| select | Manually switch between multiple nodes/sub-groups, no automatic decision-making | Everyday manual node picking |
| url-test | Periodically tests latency against a given URL and automatically picks the fastest node | Automatic best-node selection |
| fallback | Probes availability in list order; only moves to the next node once the current one fails | Primary/backup failover |
| load-balance | Distributes connections across multiple nodes using hashing or round-robin | Spreading load across nodes |
proxy-groups:
- name: "Auto Select"
type: url-test
proxies:
- HK-01
- SG-02
url: "http://www.gstatic.com/generate_204"
interval: 300
- name: "Manual Select"
type: select
proxies:
- Auto Select
- HK-01
- SG-02
- DIRECT
Policy groups can reference each other and nest (as above, where "Manual Select" lists "Auto Select" as one of its options), but circular references are not allowed — the core will refuse to start and throw an error the moment it detects a loop while loading the config.
The rules section: order and matching logic
The rules section is a list matched top to bottom in order. For every network request, the core checks each rule in sequence and applies the policy from the first rule that matches — it never keeps checking further down the list. That means the order of the rules is itself part of the logic: get the order wrong and a more specific rule further down will never get a chance to fire.
rules:
- DOMAIN-SUFFIX,google.com,Auto Select
- DOMAIN-KEYWORD,github,Auto Select
- DOMAIN,ad.example.com,REJECT
- GEOIP,CN,DIRECT
- MATCH,Manual Select
Common match types include DOMAIN (exact domain match), DOMAIN-SUFFIX (domain suffix, which also matches subdomains), DOMAIN-KEYWORD (matches if the domain contains the keyword), IP-CIDR (matches by IP range), GEOIP (matches by the country/region an IP belongs to), and MATCH (the catch-all rule, placed on the last line, matching any request that didn't hit an earlier rule). Forgetting the MATCH catch-all line is a common oversight — without it, some requests find no matching policy at all and fall back to unpredictable default behavior.
The target on the right side of a rule must be either a policy group name already defined in proxy-groups, or one of the built-ins: DIRECT (bypass the proxy) or REJECT (block the connection, commonly used for ad domains). Policy group names are case-sensitive, and a rule can't reference a group that was never declared — that also throws an error at load time.
RULE-SET rule, you need to declare that rule set's source URL and local cache path in the rule-providers section first — otherwise the rules table will fail to load because it references a rule set that doesn't exist yet.
Common indentation mistakes and how to find them
YAML is stricter about indentation than most config formats. These are the mistakes that come up most often when editing config files by hand:
- Sibling fields with inconsistent indentation
Every item under the same list must use exactly the same number of leading spaces. Even a single space off, and the core treats it as a structural error and refuses to load the entire file — not just skip the offending line.
- Mixing tabs and spaces
Most text editors default to tab indentation, but the YAML spec doesn't accept tab characters. Use an editor with a "convert tabs to spaces" option, and standardize on two spaces per indentation level.
- Missing space after the colon
YAML key-value pairs require a space after the colon before the value.
name:HK-01without that space parses as an invalid key — it needs to be written asname: HK-01. - Unquoted strings getting misinterpreted
If a password, UUID, or similar field starts with a digit or contains special characters, wrap it in double quotes explicitly. Otherwise it may get parsed as a number or boolean instead of a string, which breaks authentication.
The fastest way to track down these issues is to run the edited config through any online YAML syntax checker first to catch indentation problems, and only then let the client load it. The client's startup log (or its output once you set log-level to debug) usually points to the exact line number where loading failed — hunting by line number is much faster than eyeballing every section. Before editing a config, back up the original file; if the client fails to load after your changes, you can just roll back to the backup instead of losing connectivity while you keep troubleshooting.