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,PROXYThat config runs. Now each field.
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 API2. 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: CNThe 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.com4. 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,🐟 FallthroughThe 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: 86400behavior 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: 300Then 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: 1500The mechanics are in TUN internals: virtual adapter, routing table and DNS hijacking.
The three YAML mistakes everyone makes
- Indent with spaces, not tabs. Most parse errors are this.
- A space after the colon.
port:443is wrong;port: 443is right. - 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
What each policy group actually does, when to use it, the parameters that matter, plus a grouping structure you can copy wholesale and Mihomo's include-all and filter options.
Editing the downloaded config gets undone at the next update. How Clash Verge's extended config works: prepend/append/override syntax, merge order, and a set of useful snippets.
What url-test measures, what unified-delay does, why the displayed latency does not match ping, and recommended values for the three parameters that matter along with their side effects.