Skip to main content
EN

Home / Blog / Config Basics

The YAML structure of a Clash config — what each of the eight top-level fields does

Config Basics2026-08-021280 words3 min read
The YAML structure of a Clash config — what each of the eight top-level fields does

What a subscription URL returns is a YAML file. Understand it and you can change it.

This walks through the top-level fields in order, with pasteable snippets for each.

A minimal working config

The whole thing first, then piece by piece:

mixed-port: 7897
allow-lan: false
mode: rule
log-level: info
ipv6: false
external-controller: 127.0.0.1:9090

dns:
  enable: true
  enhanced-mode: fake-ip
  fake-ip-range: 198.18.0.1/16
  nameserver: [223.5.5.5, 119.29.29.29]
  fallback: [https://1.1.1.1/dns-query]

proxies:
  - name: "HK-01"
    type: trojan
    server: hk01.example.com
    port: 443
    password: "your-password"
    udp: true

proxy-groups:
  - name: "PROXY"
    type: select
    proxies: ["AUTO", "HK-01"]
  - name: "AUTO"
    type: url-test
    proxies: ["HK-01"]
    url: "http://www.gstatic.com/generate_204"
    interval: 300

rules:
  - DOMAIN-SUFFIX,cn,DIRECT
  - GEOIP,CN,DIRECT
  - MATCH,PROXY

That config runs. Now each field.

How responsibility is divided between the top-level fields1Basic settingsports, mode, logging, LAN switch — how the client listens2dnshow names are resolved — which IP you end up going to3proxieswhat nodes exist — the list of exits4proxy-groupshow a node is chosen — the policy layer5ruleswhich traffic takes which policy — the decision layer6rule-providersremote rule sets — rules from outside
Top to bottom: first it runs, then it resolves, then it has exits, then policies, then rules

1. Basic settings

mixed-port: 7897          # shared HTTP + SOCKS5 port (recommended)
# port: 7890              # separate HTTP port, rarely needed
# socks-port: 7891        # separate SOCKS5 port, rarely needed
redir-port: 7892          # transparent proxy (Linux/macOS)
tproxy-port: 7893         # TProxy (Linux)
allow-lan: false          # whether LAN devices may connect
bind-address: "*"         # listening address when allow-lan is true
mode: rule                # rule / global / direct
log-level: info           # silent / error / warning / info / debug
ipv6: false               # enable IPv6
unified-delay: true       # unified latency calculation, closer to reality
tcp-concurrent: true      # concurrent handshakes, faster for multi-IP domains
external-controller: 127.0.0.1:9090
secret: ""                # password for the control API

2. dns

The DNS section decides "what IP does this name resolve to", which directly affects routing accuracy and whether streaming platforms unlock.

dns:
  enable: true
  listen: 0.0.0.0:1053
  ipv6: false
  enhanced-mode: fake-ip           # fake-ip or redir-host
  fake-ip-range: 198.18.0.1/16
  fake-ip-filter:                  # these names skip fake-ip
    - "*.lan"
    - "*.local"
    - "+.pool.ntp.org"
    - "localhost.ptlogin2.qq.com"
  default-nameserver: [223.5.5.5]  # used to resolve the DoH hostnames below
  nameserver:                      # primary DNS
    - 223.5.5.5
    - https://doh.pub/dns-query
  fallback:                        # overseas DNS
    - https://1.1.1.1/dns-query
    - tls://8.8.4.4:853
  fallback-filter:
    geoip: true
    geoip-code: CN

The two values of enhanced-mode behave very differently; there is a whole article on it: DNS configuration and how fake-ip works.

3. proxies

The node list. Fields differ per protocol; here are the common ones:

proxies:
  # Trojan
  - name: "HK-Trojan"
    type: trojan
    server: hk.example.com
    port: 443
    password: "pwd"
    sni: hk.example.com
    udp: true

  # VMess
  - name: "JP-VMess"
    type: vmess
    server: jp.example.com
    port: 443
    uuid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    alterId: 0
    cipher: auto
    tls: true
    network: ws
    ws-opts:
      path: /ws
      headers: { Host: jp.example.com }

  # Shadowsocks
  - name: "SG-SS"
    type: ss
    server: sg.example.com
    port: 8388
    cipher: aes-256-gcm
    password: "pwd"
    udp: true

  # Hysteria2 (supported by Mihomo)
  - name: "US-HY2"
    type: hysteria2
    server: us.example.com
    port: 443
    password: "pwd"
    sni: us.example.com

4. proxy-groups

Policy groups decide "which node does this class of traffic take". The differences between the five types are covered in proxy-group types explained; here is a structure that works:

proxy-groups:
  - name: "🚀 Select"
    type: select
    proxies: ["♻️ Auto", "🇭🇰 Hong Kong", "🇯🇵 Japan", "DIRECT"]

  - name: "♻️ Auto"
    type: url-test
    include-all: true            # Mihomo: pull in every node automatically
    filter: "HK|Hong"            # keep only what matches
    url: "http://www.gstatic.com/generate_204"
    interval: 300
    tolerance: 50

  - name: "🎬 Streaming"
    type: select
    proxies: ["🇭🇰 Hong Kong", "🇸🇬 Singapore", "🚀 Select"]

  - name: "🐟 Fallthrough"
    type: select
    proxies: ["🚀 Select", "DIRECT"]

include-all plus filter is a genuinely useful Mihomo feature: no hand-written node names, just a regex that collects them. Syntax in node naming and grouping regex.

5. rules

Matched top to bottom; the first rule that hits wins.

rules:
  # LAN direct (must come first)
  - IP-CIDR,127.0.0.0/8,DIRECT,no-resolve
  - IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
  - IP-CIDR,10.0.0.0/8,DIRECT,no-resolve

  # specific domain rules
  - DOMAIN-SUFFIX,openai.com,🚀 Select
  - DOMAIN-SUFFIX,netflix.com,🎬 Streaming
  - DOMAIN-KEYWORD,google,🚀 Select

  # rule sets
  - RULE-SET,cn-domain,DIRECT
  - RULE-SET,proxy-domain,🚀 Select

  # geographic catch-all
  - GEOIP,CN,DIRECT
  - MATCH,🐟 Fallthrough

The full rule-type table is in the rule type reference.

6. rule-providers

Keep rule lists in external files that refresh on a schedule:

rule-providers:
  cn-domain:
    type: http
    behavior: domain
    format: text
    url: "https://example.com/rules/cn.txt"
    path: ./ruleset/cn-domain.txt
    interval: 86400

  private-ip:
    type: http
    behavior: ipcidr
    url: "https://example.com/rules/private.txt"
    path: ./ruleset/private.txt
    interval: 86400

behavior has three values: domain (a list of names), ipcidr (IP ranges) and classical (full rule syntax). Getting it wrong makes the rules silently do nothing without any error — a classic trap. See the complete rule-providers guide.

7. proxy-providers

Nodes can come from outside too:

proxy-providers:
  main:
    type: http
    url: "your subscription URL"
    interval: 3600
    path: ./providers/main.yaml
    health-check:
      enable: true
      url: http://www.gstatic.com/generate_204
      interval: 300

Then reference it in a group with use: [main]. This is the right shape for merging several subscriptions; see managing and merging multiple profiles.

8. tun

TUN configuration (Clash Verge has UI for this, but knowing what sits underneath helps):

tun:
  enable: true
  stack: mixed                 # gvisor / system / mixed
  auto-route: true
  auto-detect-interface: true
  dns-hijack: ["any:53"]
  strict-route: false
  mtu: 1500

The mechanics are in TUN internals: virtual adapter, routing table and DNS hijacking.

The three YAML mistakes everyone makes

Things to watch when writing YAMLIndent with spaces only, never tabs — a tab causes an outright parse failure
The line number in the error message is usually accurate — jump straight there
  1. Indent with spaces, not tabs. Most parse errors are this.
  2. A space after the colon. port:443 is wrong; port: 443 is right.
  3. Quote special characters. A password containing @, # or : must be quoted: password: "p@ss#123".

The full error reference is in YAML error troubleshooting.

The right way to modify a config

Editing the file your subscription downloaded means the next auto-update overwrites it.

The correct approach is Clash Verge's "Global Extended Config / Merge", which appends without touching the original:

# global extended config
prepend-rules:
  - DOMAIN-SUFFIX,mycompany.com,DIRECT

append-rules:
  - DOMAIN-SUFFIX,example.net,🚀 Select

prepend-proxy-groups:
  - name: "My group"
    type: select
    proxies: ["DIRECT", "🚀 Select"]

See extending a subscription with Merge.

In short

Eight top-level fields with clean responsibilities: basic settings handle listening, dns handles resolution, proxies handles exits, proxy-groups handles policy, rules handles decisions, the two providers handle external sources, and tun handles network-layer interception.

Before changing a config, work out which layer you are changing — then use Merge rather than editing the subscription file directly.


Related docs