The config file at its core: a YAML document
Every behavior of the Clash and mihomo (Clash Meta) core is driven by a single YAML config file. The file your subscription link downloads is essentially a YAML document your provider has already written; the mode switches and node selections in your client's UI ultimately just edit fields in that same file. Once you understand the structure, changing ports, adding rules, or tweaking DNS is just a matter of finding the right section and editing a few lines.
YAML syntax boils down to three rules: use spaces only for indentation, never tabs, and indent by two spaces per level; keys and values are separated by a colon, and there must be a space after the colon; lines starting with a dash are list items, and anything after a # is a comment. A full config is built from several top-level fields, listed below along with what they do.
| Top-level field | Purpose |
|---|---|
| mixed-port | Mixed proxy port shared by HTTP and SOCKS5 requests |
| allow-lan | Whether other devices on the LAN can connect |
| mode | Working mode: rule-based / global / direct |
| log-level | Verbosity of log output |
| external-controller | Address the core's API listens on, used by web dashboards |
| dns | Built-in DNS resolution settings |
| proxies | List of proxy nodes |
| proxy-groups | Proxy groups, the items you switch between in the client UI |
| rules | Routing rules, matched top to bottom |
| tun | TUN virtual network adapter mode (mihomo only) |
Of these, proxies, proxy-groups, and rules together decide how traffic is routed, and they're the focus of this article; dns decides how domains resolve; the remaining fields control ports and runtime behavior. Let's go through each section.
Ports and global fields: mixed-port through external-controller
mixed-port: 7890
allow-lan: false
bind-address: "*"
mode: rule
log-level: info
external-controller: 127.0.0.1:9090
mixed-port is the mixed port: both HTTP and SOCKS5 requests enter the core through it, and most clients and subscriptions default to 7890. Older configs that split this into port (HTTP only) and socks-port still work, but a new config only needs mixed-port.
allow-lan: when set to true, other phones or tablets on the same LAN can use this machine as a proxy gateway;bind-addresscontrols which network interface it listens on. Keep this false on public networks.mode: rule routes traffic per the rules section; global sends everything through the selected proxy group; direct sends everything straight through with no proxy. The mode switch in the client UI just changes this value.log-level: info is fine for everyday use; switch to debug temporarily when troubleshooting connections; silent suppresses all output.external-controller: the address the core's RESTful API listens on, used by web dashboards like metacubexd and yacd to read status and switch nodes; add asecretline to set an access key so the port can't be controlled by anyone who finds it exposed.
mihomo configs also commonly include extension fields like unified-delay, tcp-concurrent, and find-process-mode, which are exclusive to the Meta core; vanilla Clash will error out on keys it doesn't recognize, so be careful when switching between cores.
The dns section: how domains get resolved
dns:
enable: true
listen: 0.0.0.0:1053
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
nameserver:
- 223.5.5.5
- 119.29.29.29
fallback:
- tls://1.1.1.1
- https://dns.google/dns-query
fake-ip-filter:
- "*.lan"
- "*.local"
enable is the master switch and must be on when using TUN mode; listen is the address the built-in DNS service listens on — the system sends domain lookups here, and the core forwards them upstream per your config.
enhanced-mode: in fake-ip mode the core returns a fake IP first, then routes by domain once the connection is established, which is fast and accurate; redir-host is the legacy mode that's being phased out in newer cores, so stick with fake-ip.fake-ip-range: the fake IP address pool, defaulting to 198.18.0.1/16; you generally don't need to change this.nameserver: the default upstream resolver, supporting three formats — a plain IP uses UDP, atls://prefix uses DoT, and anhttps://prefix uses DoH.fallback: the upstream used for resolving overseas domains, a legacy approach; mihomo instead recommendsnameserver-policy, which lets you assign different upstreams by domain suffix group.fake-ip-filter: domains on this list never get a fake IP — commonly used for LAN hostnames and certain QR-code login domains.
When this actually applies
The dns section only matters when the core is handling your traffic. In plain system-proxy mode, the browser resolves domains itself and this section is bypassed; in TUN mode, every lookup passes through the core, so this section fully takes effect.
The proxies section: each dash is one node
proxies:
- name: "Hong Kong 01"
type: ss
server: hk1.example.com
port: 8388
cipher: aes-128-gcm
password: "example-password"
udp: true
- name: "Japan 01"
type: vmess
server: jp1.example.com
port: 443
uuid: 00000000-0000-0000-0000-000000000000
alterId: 0
cipher: auto
tls: true
network: ws
proxies is the node list, where each dash-prefixed entry is one node. name is the display name, type is the protocol, and server plus port give the server address; the remaining fields depend on the protocol: ss needs cipher and password; vmess needs uuid, alterId, and cipher; vless needs uuid; trojan needs password and sni; hysteria2 needs password. udp: true allows UDP traffic to be forwarded.
mihomo supports more protocols than vanilla Clash: the original supports ss, ssr, vmess, trojan, snell, and a few others, while the Meta core adds vless, hysteria, hysteria2, tuic, and more. This section is almost always auto-generated by your subscription, so before editing it by hand, keep one thing in mind: any manual changes get overwritten the next time the subscription updates.
The proxy-groups section: what you actually switch in the UI
proxy-groups:
- name: "Auto Select"
type: url-test
proxies:
- "Hong Kong 01"
- "Japan 01"
url: "http://www.gstatic.com/generate_204"
interval: 300
tolerance: 50
- name: "Manual Select"
type: select
proxies:
- "Auto Select"
- "Hong Kong 01"
- "Japan 01"
- DIRECT
What you click in the client UI is never a single node — it's a proxy group. type comes in four flavors: select for manual choice; url-test to auto-pick the fastest by latency; fallback to switch through nodes in order based on availability; load-balance to spread connections across multiple nodes.
url: the target address used for latency testing, commonly http://www.gstatic.com/generate_204.interval: the auto speed-test interval in seconds; 300 means testing every five minutes.tolerance: the tolerance margin in milliseconds — a newly tested node has to be faster than the current one by more than this amount before the group switches, which prevents flip-flopping.
A group's proxies list can contain either node names or other group names, letting you nest them like "Manual Select → Auto Select → several nodes"; you can also include the built-in DIRECT and REJECT policies. Rules reference group names, so as nodes get swapped out by subscription updates, the group structure stays the same.
The rules section: top to bottom, first match wins
rules:
- DOMAIN-SUFFIX,ads.example.com,REJECT
- DOMAIN-KEYWORD,bilibili,DIRECT
- GEOSITE,cn,DIRECT
- IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
- GEOIP,CN,DIRECT,no-resolve
- MATCH,Manual Select
Rules are matched top to bottom, and matching stops at the first hit — so order is priority. The MATCH rule at the very end is the catch-all, picking up any traffic that didn't match an earlier rule. Each rule has three parts: type, match value, and target policy. The target policy can be a proxy group name, a node name, or the built-in DIRECT or REJECT.
| Rule type | Matches against | Example |
|---|---|---|
| DOMAIN | Exact domain | DOMAIN,www.example.com,PROXY |
| DOMAIN-SUFFIX | Domain suffix | DOMAIN-SUFFIX,google.com,PROXY |
| DOMAIN-KEYWORD | Domain keyword | DOMAIN-KEYWORD,bilibili,DIRECT |
| GEOSITE | Domain category database (mihomo only) | GEOSITE,cn,DIRECT |
| IP-CIDR | Destination IP range | IP-CIDR,10.0.0.0/8,DIRECT,no-resolve |
| GEOIP | IP geolocation | GEOIP,CN,DIRECT |
| DST-PORT | Destination port | DST-PORT,443,PROXY |
| PROCESS-NAME | Process name | PROCESS-NAME,telegram.exe,PROXY |
| MATCH | Catch-all, matches everything | MATCH,PROXY |
Two things trip people up. First, IP-based rules like IP-CIDR and GEOIP trigger a DNS lookup when matched against a domain request; adding the no-resolve parameter avoids resolving early and leaves the decision to later domain-based rules. Second, GEOSITE is only supported by mihomo and depends on the geosite data file — vanilla Clash will error out if it encounters one; GEOIP is supported by both cores, though the data file format differs between them.
How changes take effect, and common errors
On a desktop client, just hit reload on the config page after editing — mihomo supports hot reload, so there's no need to restart the app; a command-line mihomo instance can push a new config through the API to update.
Where YAML errors usually come from
Tabs mixed into the indentation; a full-width colon typed by mistake; a missing space after a colon; a list item indented differently from the one above it; a node name containing a colon or special character without quotes around it. When the client fails to start and shows "yaml: line xx", go back and check these exact things at that line — that's where almost every issue lives.
Two other common issues: first, port conflicts — if something else is already using 7890, the core fails to start, so change mixed-port or close the conflicting process; second, edited rules that don't seem to take effect, usually because a broader rule earlier in the list is matching first — set log-level to debug, and the rule-matching entries in the log will show exactly which rule each connection actually hit.
The config file structure really only has these pieces: the port fields define the entry point, dns handles resolution, proxies is the node list, proxy-groups are the switches you toggle, and rules is the dispatch table. Read through your own subscription config in that order and every line will make sense.