RENT YOUR BANNER
YOUR BANNER WILL BE PLACED HERE
CLICK
RENT YOUR BANNER
YOUR BANNER WILL BE PLACED HERE
CLICK
Study Tips and Guides

Clash Config Files Explained

Sooner or later you open the file. Maybe a subscription generated it, maybe a colleague sent it over, maybe you exported it from a client out of curiosity. Either way you are looking at several hundred lines of YAML containing server names, cipher strings, group definitions and a wall of comma-separated rules, and none of it announces where to start reading.

The file is simpler than its length implies. Most of those lines are repetition — one server entry looks much like the next — and the real structure is a short list of top-level keys, each with a bounded job. Once you can name the keys and say what each is responsible for, editing stops being guesswork.

This article walks through the file key by key, with realistic snippets, and then covers the two things that cause the most wasted time: the syntax mistakes that stop a profile loading, and the habits that keep a configuration readable after it has grown.

The Top-Level Keys at a Glance

A configuration file is a single YAML mapping. Every key sits at column zero, and everything indented beneath it belongs to that key. The ones you will actually deal with are:

  • portsocks-portmixed-port — the local listeners applications connect to.
  • allow-lanbind-address — whether other machines on your network may use those listeners.
  • mode — whether the rule list is consulted at all.
  • log-level — how much detail the engine writes out.
  • external-controllersecret — the local API a graphical client talks to.
  • dns — how hostnames are resolved before or during routing.
  • proxies — your outbound servers, written out in full.
  • proxy-providers — outbound servers pulled in from an external file or URL.
  • proxy-groups — named policies that choose between outbounds.
  • rules — the ordered decision list.
  • rule-providers — rule lists maintained outside this file.

YAML does not care what order those keys appear in. Convention puts the small scalar settings first and the long lists last, purely because scrolling past four hundred server entries to reach log-level is miserable.

The Small Settings at the Top

Listeners

mixed-port: 7890
allow-lan: false
bind-address: "127.0.0.1"
ipv6: false

Older files declare port for HTTP and socks-port for SOCKS5 separately. mixed-port replaces both with one listener that accepts either protocol, and unless you have a reason to separate them, it is the tidier choice.

allow-lan deserves a moment of thought. Set true, other devices can point their proxy settings at your machine and use your rules — useful for a phone or console that cannot run a client. It also means anything else on that network can do the same, so leave it false unless you want that.

mode

mode: rule

Three values are accepted. rule evaluates the rule list, which is the reason the file exists. global ignores the rules and sends everything through one outbound. direct ignores them the other way, sending everything out untouched. The last two are diagnostic modes; testing a rule change while in global tests nothing.

log-level and the local API

log-level: info
external-controller: 127.0.0.1:9090
secret: "choose-something-non-empty"

Log levels run from silent through errorwarninginfo to debug. Leave it on info day to day and raise it only while chasing a problem, since debug produces a great deal of output very quickly.

The external controller is an HTTP API on localhost. Desktop clients use it to read connections, switch groups and trigger latency tests. Bind it to 127.0.0.1 and set a non-empty secret; an API listening on all interfaces with no secret is an open remote control for your routing.

The dns Block

DNS is the part people skip and then spend an evening debugging. If names are resolved in the wrong place, rules that look correct will match the wrong thing.

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"
    - "localhost.ptlogin2.qq.com"
  nameserver:
    - 223.5.5.5
    - 8.8.8.8
  fallback:
    - https://dns.google/dns-query

The field that matters is enhanced-mode. Under fake-ip, the engine hands the application a placeholder address and keeps the real hostname for itself, so domain rules still work for applications that resolve names before connecting. Under redir-host it resolves normally and matches on the result. Anything that must be resolved locally — internal hosts, a captive portal — belongs in fake-ip-filter.

proxies: Writing Out Your Outbounds

Each item under proxies is one server. name and type are always required; the remaining fields depend on the protocol.

proxies:
  - name: "osaka-a"
    type: trojan
    server: osaka-a.example.net
    port: 443
    password: "example-password"
    sni: osaka-a.example.net
    skip-cert-verify: false

  - name: "paris-b"
    type: ss
    server: paris-b.example.net
    port: 8388
    cipher: aes-256-gcm
    password: "example-password"
    udp: true

Two details save trouble later. Names are referenced verbatim elsewhere in the file, so a stray space or changed capital breaks every group and rule pointing at them. And udp: true matters for anything real-time — voice, video, some games — since without it those flows fail quietly while web traffic looks fine.

proxy-providers: Not Maintaining the List by Hand

Writing out fifty servers is tedious and goes stale. A provider fetches the list from a URL, caches it locally and refreshes it on a schedule.

proxy-providers:
  main:
    type: http
    url: "https://example.net/subscription-path"
    path: ./providers/main.yaml
    interval: 86400
    health-check:
      enable: true
      url: http://www.gstatic.com/generate_204
      interval: 600

A group then consumes the provider with use: instead of listing names. New servers appear automatically at the next refresh; removed ones disappear without leaving broken references behind, which is the main practical reason to prefer providers over a hand-written list.

proxy-groups: Nodes Become Policies

A group is a named decision. Rules refer to the group, the group refers to servers, and that indirection is what lets your server list churn without touching a single rule.

proxy-groups:
  - name: "Fast"
    type: url-test
    use: ["main"]
    url: "http://www.gstatic.com/generate_204"
    interval: 300
    tolerance: 60

  - name: "Backup"
    type: fallback
    proxies: ["osaka-a", "paris-b"]
    url: "http://www.gstatic.com/generate_204"
    interval: 180

  - name: "Spread"
    type: load-balance
    use: ["main"]
    strategy: consistent-hashing

  - name: "Pick"
    type: select
    proxies: ["Fast", "Backup", "Spread", "DIRECT"]

A group may take proxiesuse, or both — explicit names plus everything a provider supplies. The filter field accepts a regular expression against server names, which is how people build per-region groups out of one large provider list without enumerating anything.

Naming groups after intent rather than geography is the single habit that keeps a growing clash config legible: a rule pointing at Media or Work still makes sense a year later, whereas one pointing at Region-3 requires you to remember what region three was for.

rules: Order Is the Program

Each rule is one line of comma-separated fields: type, value, target, and occasionally a modifier. The list is evaluated strictly from top to bottom and the first match wins outright.

rules:
  - DOMAIN-SUFFIX,local,DIRECT
  - IP-CIDR,127.0.0.0/8,DIRECT,no-resolve
  - IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
  - RULE-SET,ads,REJECT
  - PROCESS-NAME,Docker,DIRECT
  - DOMAIN-SUFFIX,githubusercontent.com,Fast
  - DOMAIN-KEYWORD,steamcontent,Spread
  - GEOIP,PRIVATE,DIRECT,no-resolve
  - MATCH,Pick

Rule typeMatches onExample valueDOMAINOne exact hostnameapi.example.comDOMAIN-SUFFIXA domain and all subdomainsexample.comDOMAIN-KEYWORDSubstring of the hostnameanalyticsIP-CIDRDestination address range10.0.0.0/8GEOIPCountry or category of the addressPRIVATEPROCESS-NAMEThe program that opened the socketsshRULE-SETA named external listadsMATCHEverything left over(no value)

Two conventions follow from first-match-wins. Specific rules go above general ones, because a broad DOMAIN-KEYWORD placed early will swallow traffic you meant to handle further down. And MATCH is always the final line — it is the default branch, and without it traffic that matched nothing has nowhere defined to go.

The no-resolve modifier is worth understanding properly. An IP-based rule cannot be tested against a request that only carries a hostname, so by default the engine performs a DNS lookup to evaluate it. no-resolve tells it to skip the rule instead of resolving, which avoids a lookup you may not want and keeps evaluation fast.

Shadowed rules are the quiet failure mode here, because a rule that never fires produces no error to read. Desktop clients that render the loaded rule list next to a live connections log make the problem visible — the guide pages at clash-vergerev.co document one cross-platform example — so you can see which line actually claimed a request instead of guessing which one should have.

rule-providers: Rule Lists Kept Elsewhere

rule-providers:
  ads:
    type: http
    behavior: domain
    url: "https://example.net/lists/ads.yaml"
    path: ./rulesets/ads.yaml
    interval: 86400

A rule provider is a list of match values with no target attached; the target is supplied by the RULE-SET rule that references it. behavior must describe the list’s contents — domainipcidr or classical — and a mismatch here silently produces a set that matches nothing.

Errors That Cost an Afternoon

  • Tabs. YAML forbids tabs for indentation, and the resulting parse error rarely mentions tabs.
  • Inconsistent indentation. Two spaces throughout is the norm. Mixing two and four inside one block changes what belongs to what.
  • Unquoted special characters. A password containing :#@ or a leading digit needs quotes, or YAML reads it as structure rather than text.
  • Spaces inside rule lines.DOMAIN-SUFFIX, example.com, Pick is not the same as the unspaced form; keep rule fields tight against their commas.
  • Names that do not match. A group listing a server that no longer exists, or misspelling one, fails at load time — the usual breakage after a provider changes.
  • A missing final MATCH. Everything works until a request matches nothing, and then the failure looks unrelated to the rules.
  • Duplicate keys. Two rules: blocks do not merge — the second replaces the first, usually silently.

Habits That Keep It Maintainable

  1. Comment in blocks, not lines. A # --- local network --- header above each group of rules beats a comment on every line.
  2. Group rules by intent. Local addresses, then blocked destinations, then application-specific routes, then broad regional rules, then MATCH. Reading the file top to bottom should tell the story.
  3. Keep your rules separate from a subscription’s. Most clients support an override or merge layer so your edits survive the next refresh instead of being overwritten by it.
  4. Put the file under version control. A config is text, and a diff instantly answers the question “what did I change before this stopped working”.
  5. Change one thing at a time. Reload, confirm the behaviour, then move on. Batched edits make attribution impossible.
  6. Prefer providers to long inline lists. The shorter the file you personally maintain, the less of it can go stale.

None of this needs special tooling, but a profile editor and a rules viewer make the habits easier to keep. Reading the list the engine actually loaded is quicker than inferring it from behaviour.

Common Questions

Does the order of top-level keys matter?

No. YAML mappings are unordered, so rules may appear before proxies without changing anything. Order matters enormously inside the rules list, and not at all outside it.

Why does the same file behave differently in two clients?

Different clients are built on different cores, and cores vary in which optional keys and rule types they implement. A field a newer core understands may be ignored by an older one. Checking the log right after loading a profile usually reveals what was skipped.

How do I test a change without breaking my working setup?

Copy the profile, edit the copy, switch to it, and switch back if it fails to parse. Keeping a known-good profile permanently in the list costs nothing and turns a bad edit into a two-click recovery.

Can one file be used on several machines?

Yes, with one caveat. Everything except PROCESS-NAME rules is portable across Windows, macOS and Linux, since executable names differ between systems. Some people keep process rules in a separate override layer for exactly that reason.

Final Word

Once the keys have names in your head, the file stops being a wall of YAML and becomes four questions asked in sequence: which servers exist, how do I group them, how do I decide between the groups, and what should happen to anything I did not think about. Everything else is detail hanging off those four.

Start from something small that works — a handful of servers, two groups, ten rules ending in MATCH — and grow it deliberately. A configuration you can still read six months from now is worth considerably more than a clever one you cannot.

About the author

admin

Leave a Comment