ADVANCED CONFIG
Clash Advanced Config Guide
Proxy groups, rule-providers, DNS, TUN, sniffing, overrides, and the external controller — everything beyond the subscription itself, covered in seven chapters with copy-ready YAML examples.
7 chapters · Jump to what you need · Examples based on mihomo core fields
This page is a reference guide, distinct from the setup tutorial: the tutorial takes you from zero to working — importing a subscription, choosing a mode, verifying the proxy. This page covers what comes after — how to build proxy groups, turn rules into subscriptions, keep DNS from slowing you down, decide when to enable TUN, fill gaps with sniffing, merge multiple subscriptions, and safely expose the external controller. The seven chapters stand on their own — jump to whichever one you need using the contents above; there's no need to read straight through.
Examples use YAML fields from the mihomo (Clash Meta) core, which mainstream clients like Clash Plus, Clash Verge Rev, and FlClash all parse the same way; a few fields don't exist in the legacy Clash core, which is now archived, and this is noted inline where relevant. If you haven't installed a client yet, grab one for your platform from the Download Center first; for differences between clients, see the client comparison.
Proxy Group Types in Practice
Proxy groups are the backbone of a Clash config. Every packet of traffic ultimately answers one question: which exit does it take? rules route traffic into a proxy group, and the proxy group decides which exit to use. A default subscription usually ships with just two groups — "Proxy Select" and "Auto Select" — workable, but not exactly convenient. Understanding all five group types is what lets you build setups like "streaming through Hong Kong, downloads through Japan, automatic failover to a backup line."
The five types at a glance
| Type | Selection method | Typical use |
|---|---|---|
| select | Manually picked from the client UI | Main exit, per-service exits |
| url-test | Periodic latency test, picks the lowest | Hands-off automatic exit selection |
| fallback | Picks the first available in list order | Primary line with automatic failover |
| load-balance | Distributes connections across nodes by strategy | Parallel lines, spreading load |
| relay | Traffic passes through every node in the group, in order | Chained proxying: front relay plus exit node |
All five types can be nested inside each other — the proxies list of one group can name another group. In practice, the most stable structure is three layers: a service-level group (e.g., "Streaming," "Downloads") points to an exit-level group, which in turn points to nodes or a url-test group, while rules only ever reference the service-level groups. This way, changing exits doesn't touch your rules, and changing nodes doesn't touch your exits — the whole setup stays easy to maintain.
select vs. url-test: manual choice vs. automatic best-pick
select does no testing at all — whatever you pick in the client UI is what gets used, which makes it a good final decision layer. url-test runs a latency check on every node in the group on a schedule set by interval, and switches the exit to whichever node is currently fastest. Two parameters decide whether it behaves well: tolerance is the switch threshold in milliseconds — if a candidate node is only a few dozen ms faster than the current one, it won't switch, which prevents flapping between two similarly fast lines; setting lazy to true pauses testing whenever no traffic is flowing through the group, skipping pointless probe requests.
proxy-groups:
- name: "Final Exit"
type: select
proxies: ["Auto Select", "Manual Pick", "DIRECT"]
- name: "Auto Select"
type: url-test
use: ["airport-a"]
url: "https://www.gstatic.com/generate_204"
interval: 300
tolerance: 80
lazy: true
- name: "Manual Pick"
type: select
include-all: true
filter: "HK|Hong Kong"
use references a subscription defined under proxy-providers; include-all: true pulls in every node from your config; filter applies a regex to narrow it down further. The three can stack — pull everything in, filter with regex, then merge into the subscription. For the test URL, use a lightweight endpoint like generate_204 that returns almost no body; interval shouldn't go below 120 seconds — frequent testing is itself meaningful bandwidth usage, and some providers will even throttle you for it.
fallback and load-balance: primary/backup and parallel
fallback treats list order as priority: health checks start from the first entry, and whichever passes first gets used; if the primary goes down, it automatically falls back to the backup, then switches back once the primary recovers. It's a good fit for "one primary dedicated line plus one backup," and respects manual ordering more than url-test does. load-balance takes the opposite approach — instead of chasing the single best line, it spreads connections across every healthy node in the group. With strategy set to consistent-hashing, the same target domain always hashes to the same node, keeping login sessions stable; with round-robin, each connection rotates to the next node, maximizing throughput but possibly causing IP changes mid-session on the same site; mihomo also offers sticky-sessions, a middle ground that keeps the same source device pinned to the same node where possible.
- name: "Primary/Backup"
type: fallback
proxies: ["Dedicated Line", "Auto Select"]
url: "https://www.gstatic.com/generate_204"
interval: 180
- name: "Load Balanced"
type: load-balance
use: ["airport-a"]
strategy: sticky-sessions
url: "https://www.gstatic.com/generate_204"
interval: 300
relay: chained proxying
relay routes traffic through every node in the group in sequence before it exits, typically used as "front relay plus exit node": local-to-relay runs over an optimized line, relay-to-exit runs over a regular international connection — giving you both stability and the exit region's identity. Every hop in the chain consumes its own bandwidth and adds to total latency, so keep it to no more than two nodes. mihomo also offers a lighter equivalent: declaring dialer-proxy directly on a single node to specify which relay it exits through, without needing a separate group at all.
- name: "Relay Exit"
type: relay
proxies: ["Front Relay", "US Exit"]
Tip
When you rename a proxy group, remember to update every reference to it in rules — a mismatched name causes that rule to silently fail, with no error at all. One health-check URL works fine across the whole config; there's no need to pick a different one per group.
Rule-Providers Subscriptions
From rules to rule-providers
Early configs piled hundreds or thousands of rules directly into the rules field — hard to read, hard to edit, and every subscription refresh overwrote the whole file, wiping out any rules you'd added yourself. rule-providers pulls rules out of the config entirely, turning them into standalone rule sets that can be subscribed to by URL: the core downloads them on startup, caches them locally, and refreshes on a schedule, while rules keeps just a single reference line. This splits your config into two layers — the strategy structure lives in hand-written YAML, while the rule data is handled by a self-updating rule set.
For mature needs like blocking ads, routing streaming traffic, or exempting local networks, the community already maintains long-running rule sets — subscribing to one is far more reliable than hand-writing every rule yourself. For rules unique to your setup (a company intranet, self-hosted services), use a file-type provider pointing to a local file — it's still referenced with RULE-SET, so the management approach stays consistent either way.
Declaration format and fields
rule-providers:
adblock:
type: http
behavior: domain
format: yaml
url: "https://example.com/rules/adblock.yaml"
path: ./ruleset/adblock.yaml
interval: 86400
lan-local:
type: file
behavior: classical
path: ./ruleset/lan.yaml
type set to http subscribes via url; set to file, it reads a local file instead. path is where the local cache lives — once a download succeeds, the rule set is written to disk, and the next startup reads from cache first. interval is the refresh period in seconds; 86400 means once a day. format declares the file format: yaml is the common list-style layout, text is one entry per line, and mrs is mihomo's dedicated binary format — smallest size, fastest to load, and the best choice for large rule sets.
The three behavior types compared
| behavior | Content type | Matching cost | Best for |
|---|---|---|---|
| domain | Domain names only | Very low — hash matching | Pure domain lists like ad/tracker domains |
| ipcidr | IP ranges only | Low — prefix matching | Regional IP ranges, ISP ranges |
| classical | Full rule syntax, mixed | Evaluated entry by entry | Mixed domains, IP ranges, and ports |
behavior tells the core how to parse the file, and getting it wrong breaks the whole set: an IP range inside a domain file, or a domain inside an ipcidr file, will both trigger errors. When subscribing to a third-party rule set, check the release page for its declared type and copy the behavior and format exactly — don't guess based on the file name.
Referencing in rules and how updates work
rules:
- RULE-SET,lan-local,DIRECT
- RULE-SET,adblock,REJECT
- GEOSITE,cn,DIRECT
- GEOIP,CN,DIRECT
- MATCH,Final Exit
RULE-SET takes two arguments in order: the provider name, and the destination (a proxy group, DIRECT, or REJECT). rules are evaluated top to bottom, and the first match wins — order is priority. Put local rules and blocklists near the top, routing rules in the middle, and let GEOSITE, GEOIP, and MATCH act as the catch-all at the bottom. Misordering is the number one reason a rule appears to "not work" — anything placed after MATCH will never be reached.
Updates happen in the background: on startup, the core checks how old each http provider's cache is, and pulls a fresh copy asynchronously once it exceeds interval; if the pull fails, the old cache stays in use — rules don't get cleared, and traffic doesn't drop. To force an immediate refresh, click the update button for that provider in the panel, or delete the cache file at path and reload the config. GEOSITE and GEOIP rely on a separate database file with its own update path — see the blog post "Clash GeoIP and GeoSite Database Updates and Routing Rule Pairing" — don't confuse the two mechanisms with RULE-SET.
Tip
The same provider can be referenced by multiple rules, each pointing to a different proxy group — for example, splitting one streaming domain set across different exits by region, without subscribing to it twice.
DNS Config Tuning
Why the proxy client should take over DNS
By default, system DNS goes over your ISP's UDP port 53 — plaintext and tamperable. Two problems directly undermine proxying: first, poisoning, where queries get injected with wrong results mid-flight, giving you an IP that simply doesn't connect; second, leakage, where your ISP sees every domain you look up, which defeats the point of routing rules in the first place. There's a subtler third issue too: DNS results decide whether domain-based rules match at all — a rule written against a domain needs the correct resolution path before the core can even see that domain.
Clash's built-in DNS server exists to fold resolution into the routing pipeline itself: domestic domains get resolved directly via a domestic DoH resolver, proxied domains get resolved remotely by the exit node, and the node server's own domain gets its own dedicated resolver — avoiding the chicken-and-egg problem of "needing the proxy connected to resolve the proxy's own address." Turning on the dns field puts the entire resolution chain under the core's control.
Field breakdown
| Field | Role |
|---|---|
| default-nameserver | Resolves DoH/DoT server domains at startup — must be a plain IP |
| nameserver | Main resolver group; supports UDP, DoH, DoT, DoQ |
| proxy-server-nameserver | Dedicated resolver for the node server's own domain |
| direct-nameserver | Domains matching direct-connect rules resolve here |
| fallback | Legacy field, resolver group for proxied domains (mihomo recommends nameserver-policy instead) |
| nameserver-policy | Assigns a resolver by domain or geosite — the core of DNS-based routing |
A ready-to-copy dns config
dns:
enable: true
listen: 0.0.0.0:1053
ipv6: false
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
fake-ip-filter:
- "*.lan"
- "*.local"
- "time.*.com"
- "ntp.*.com"
- "+.stun.*.*"
default-nameserver:
- 223.5.5.5
- 119.29.29.29
proxy-server-nameserver:
- https://doh.pub/dns-query
nameserver:
- https://doh.pub/dns-query
- https://dns.alidns.com/dns-query
nameserver-policy:
"geosite:cn":
- https://doh.pub/dns-query
"geosite:geolocation-!cn":
- https://1.1.1.1/dns-query
The idea behind this config: default-nameserver uses plain IPs as a fallback, resolving the resolver domains themselves — doh.pub, dns.alidns.com, and so on. nameserver-policy splits domains into two paths by geosite: domestic domains go through a domestic DoH resolver, while everything else goes through a proxy-side DoH resolver, resolved remotely by the node. proxy-server-nameserver handles node domains separately, keeping it independent from regular traffic resolution. listen makes the core serve DNS on port 1053, which is where TUN's dns-hijack redirects system queries.
enhanced-mode: redir-host vs. fake-ip
redir-host does real resolution — forwarding queries upstream and returning the actual IP. It has the best compatibility, at the cost of an extra DNS round trip; and since the core only sees the IP, domain-based rules have to rely on a reverse cache lookup. fake-ip instead returns a fake address from the 198.18.0.1/16 range immediately — the app connects right away, and the core reconstructs the domain from its earlier "fake address to domain" mapping to match rules — skipping the real round trip, connecting faster, and matching domain rules precisely. fake-ip is the better choice for the vast majority of desktop and mobile setups; any service that genuinely needs a real IP should be excluded via fake-ip-filter. ipv6 is best left off if your connection hasn't been assigned IPv6, to avoid AAAA queries slowing down resolution overall.
Changing the dns field and having nothing happen is a common source of confusion: reloading the config is only step one — the OS and browser both cache old resolutions, so a system DNS cache flush or browser restart is needed before the new path actually takes over.
Troubleshooting
If a handful of sites break with the proxy on but work fine with it off, it's almost always a DNS routing issue: check which path nameserver-policy sent that domain down, then check whether fake-ip-filter needs an entry added.
TUN & Fake-IP
What problem TUN solves
A system proxy essentially just "tells apps where the proxy is" — apps are only covered if they choose to respect it. Browsers and most office software cooperate fine, but games, CLI tools, and plenty of desktop apps ignore system proxy settings outright, sending traffic straight over the direct connection. TUN takes a different route: it creates a virtual network adapter inside the core and uses the routing table to pull in all TCP/UDP traffic from the whole machine — apps never even notice, since they don't need proxy support and don't need individual configuration.
The tradeoff is permissions. A virtual adapter and routing table are both system-level resources: Windows needs a kernel-level service installed, macOS needs a one-time authorization, and Linux needs capabilities or root. Clients like Clash Plus and Clash Verge Rev turn this into a single toggle in settings — just complete the one-time authorization prompt. Android clients are already built as a VPNService, so they naturally operate at the TUN layer; iOS is handled by the system's Network Extension, requiring no user action at all. Grab the right client for your platform from the Download Center.
The tun field
tun:
enable: true
stack: mixed
device: Meta
mtu: 1500
dns-hijack:
- any:53
- tcp://any:53
auto-route: true
auto-detect-interface: true
| Field | Value | Description |
|---|---|---|
| stack | system / gvisor / mixed | TCP/IP stack implementation — see below |
| device | Adapter name | Defaults to Meta — rarely needs changing |
| dns-hijack | List of listen addresses | Hijacks port 53 queries into the built-in DNS server |
| auto-route | true / false | Automatically pushes routes, taking over machine-wide traffic |
| auto-detect-interface | true / false | Auto-detects the physical uplink adapter to prevent traffic loops |
| mtu | Value | Defaults to 1500; some mobile networks need it lowered to around 1428 |
stack has three options: system forwards through the OS's own protocol stack directly, giving the best performance; gvisor rebuilds connections in a userspace stack, offering the broadest compatibility and more stability on certain restrictive networks; mixed routes TCP through system and UDP through gvisor, balancing the two — and is the default for most clients. auto-route and auto-detect-interface must both be enabled together: the former pulls traffic in, the latter ensures the core's own outbound traffic still goes out over the physical adapter — skip the latter and you get a traffic loop, which shows up as total loss of connectivity right after enabling TUN.
How Fake-IP works
Fake-IP and TUN are a matched pair. When an app queries a domain, the built-in DNS server skips real resolution and instead hands back a fake address allocated from fake-ip-range (198.18.0.1/16), recording the mapping between that fake address and the domain. The app then connects to the fake address; TUN intercepts the connection, restores the domain from the mapping, matches it against the rules, and decides whether to go direct or route through a node — and when routing through a node, the domain itself is sent along for the remote side to resolve, with no real DNS lookup needed locally at any point.
This design brings two direct benefits: faster first connections, since a real DNS round trip is skipped entirely, and more accurate routing, since rule matching always works against a real domain rather than an IP. The tradeoff is the side effect of the app only ever "seeing" a fake address: local network services, NTP time sync, STUN hole-punching, some game anti-cheat systems, and banking software all need a real IP, and their domains need to be added to fake-ip-filter. filter supports wildcards: *.lan matches anything ending in .lan, and +.example.com matches both the root domain and all subdomains.
Platform-specific notes
On Windows, the client's service mode registers the core as a system service, with the TUN toggle in the settings page; on macOS, enhanced mode requires a one-time password prompt the first time it's enabled; Linux desktop clients offer the same toggle, but running mihomo directly from the command line requires granting permissions with setcap:
sudo setcap 'cap_net_admin,cap_net_bind_service=+ep' /usr/local/bin/mihomo
Once granted, a regular user can start TUN without needing to run as root the whole time. For the full server-side deployment workflow, see the blog post "Deploying Clash on Linux: Desktop Client Install and mihomo Command-Line Setup".
Tip
TUN and the system proxy are mutually exclusive — once TUN is running day to day, there's no need to also enable the system proxy. After turning on TUN, double-check that dns-hijack and enhanced-mode are configured together: in fake-ip mode, port 53 needs to be hijacked for the whole resolution loop to actually close.
Domain Sniffing
Rules need a domain, but connections don't always carry one
Clash's rule system is built primarily around domains, but connections reaching the core don't always come with a domain attached. Apps that handle their own DNS, ship with built-in DoH, cache old resolution results, or even hardcode IPs directly — these connections arrive at the core with only a target IP. Without a domain, DOMAIN, DOMAIN-SUFFIX, and GEOSITE rules all fail to match, and traffic falls all the way down to GEOIP or MATCH, badly hurting routing accuracy.
Domain sniffing reads the domain back out of the connection's own handshake data: a TLS connection's ClientHello carries an SNI field, and a plaintext HTTP request carries a Host header — both are unencrypted and readable as-is. Before forwarding, the core peeks at the handshake packet, extracts the domain, and reattaches it to the connection so later rule matching can proceed by domain. Once TUN is capturing machine-wide traffic, every connection that bypasses system DNS becomes visible to the core this way — sniffing has effectively become TUN's standard companion feature.
Config syntax
sniffer:
enable: true
parse-pure-ip: true
override-destination: true
sniff:
HTTP:
ports: [80, "8080-8880"]
override-destination: true
TLS:
ports: [443, 8443]
QUIC:
ports: [443, 8443]
skip-domain:
- "Mijia Cloud"
- "+.push.apple.com"
parse-pure-ip also attempts sniffing on pure-IP connections, which is where sniffing does most of its work; override-destination replaces a connection's target address with the sniffed domain, letting connections under redir-host mode also route by domain; under sniff, port ranges are declared per protocol, with QUIC listed separately since it extracts SNI differently than TLS-over-TCP does. skip-domain is an exemption list: known services that misbehave after sniffing (some push notification channels, smart home cloud services) go here, and the core skips sniffing for these domains entirely.
Edge cases and troubleshooting
Sniffing isn't a cure-all. Connections using ECH (Encrypted Client Hello) encrypt the SNI field, so sniffing can't retrieve the domain and falls back to IP-based rules; QUIC sniffing in some environments can cause video sites to load incorrectly, so it's worth testing with the QUIC block removed on its own; sniffing only reads a few handshake fields and never decrypts traffic, so the performance cost is negligible.
It's worth clarifying the relationship with Fake-IP: under fake-ip mode, the core already has the domain from the DNS step itself, so sniffing only matters for the rare case of apps bypassing system DNS; under redir-host mode or pure-IP direct connections, sniffing is the main safeguard for routing accuracy. If a service stops connecting after enabling sniffing, add its domain to skip-domain first and verify before troubleshooting further.
Tip
Sniffing and rules complement each other rather than replace one another. The more complete your domain rules are, the less sniffing has to compensate; conversely, relying on sniffing to carry all your routing will fail across the board the moment ECH is involved.
Local Overrides & Multi-Subscription Merging
An unavoidable tension
A subscription is a full YAML file, and the client refreshes it on a schedule — every refresh is a full replacement. Nodes you added by hand, proxy groups you tweaked, DNS settings you adjusted — all of it disappears on the next update. Keeping local changes separate from subscription content is the foundation of using Clash well over the long term. There are two layers to this: proxy-providers aggregates multiple subscriptions, decoupling nodes from subscription files; the client's override mechanism handles local edits, decoupling your patches from the subscription file. Some people use an online subscription-conversion service to merge multiple subscriptions into one, at the cost of handing your subscription URLs to a third party; proxy-providers does the same thing locally, with the URLs never leaving your machine.
proxy-providers: pooling multiple subscriptions into one node set
proxy-providers:
airport-a:
type: http
url: "https://example.com/sub-a.yaml"
path: ./providers/airport-a.yaml
interval: 86400
health-check:
enable: true
url: "https://www.gstatic.com/generate_204"
interval: 300
override:
additional-prefix: "[A] "
airport-b:
type: http
url: "https://example.com/sub-b.yaml"
path: ./providers/airport-b.yaml
interval: 86400
override:
additional-prefix: "[B] "
self-hosted:
type: file
path: ./providers/self.yaml
Each provider downloads, caches, and health-checks independently. override.additional-prefix adds a prefix to every node name from that subscription — so when two providers happen to name nodes the same thing (everyone seems to have a "Hong Kong 01"), they no longer overwrite each other, and the panel makes it obvious at a glance which provider each node came from. The filter field uses regex to keep only the nodes you actually want — say, only home-broadband lines. A file-type self-hosted provider can hold hand-written self-hosted nodes, sitting in the pool right alongside your subscriptions.
Once aggregated, proxy groups reference providers with use, mixed in alongside proxies:
- name: "Auto Select"
type: url-test
use: ["airport-a", "airport-b", "self-hosted"]
url: "https://www.gstatic.com/generate_204"
interval: 300
Client overrides: patching a subscription
Mainstream clients all offer an override layer that works the same way under the hood: the subscription YAML is downloaded, run through a user-defined patch, and only then handed to the core. Clash Plus provides an override entry point right in subscription settings; Clash Verge Rev offers two modes — Merge (combining fields by YAML path) and Script (rewriting freely via script); FlClash supports appending rules and proxy groups in its override config. What a patch can do: insert rules ahead of the rest, append proxy groups, change dns, disable ipv6 — and it keeps applying no matter how many times the subscription refreshes.
The most common use of overrides is prepending rules. Clients generally offer a slot for "append to the top of rules," and since rules at the top take priority, this is the right place for personal entries like "always direct-connect my company domain" or "force this site through a specific exit." The subscription's own rules stay untouched. If something breaks, just turn the override off to fall back to the subscription as-is — a very short path to troubleshooting. Basic subscription import and mode selection are still the prerequisite here — see the main steps in the setup tutorial.
Merge conflicts and maintenance discipline
Multi-subscription merging runs into trouble in three spots. Naming conflicts: identically named nodes from different subscriptions get deduplicated, or the later one overwrites the earlier — additional-prefix is the most reliable fix. Proxy group name clashes: if an appended override group shares a name with a group from the subscription, the override wins and the subscription's group is replaced — intentional, this is a feature; accidental, it's a bug waiting to happen. Rule ordering: prepended rules take priority over subscription rules, so a prepended rule that's too broad (like a big block of direct-connect IPs) can silently override the subscription's own routing — keep prepended entries narrow and specific.
For maintenance, it's worth consolidating all local changes into a single override file, managing rules, proxy groups, and DNS in one place with comments explaining each change. If a subscription update fails, the old cache stays in use and nodes won't suddenly vanish; for the troubleshooting order and auto-update setup, see the blog post "Common Causes of Clash Subscription Update Failures and How to Set Up Auto-Updates".
External Controller Panel
What external-controller is
Once the core starts up, it exposes a set of RESTful API and WebSocket endpoints at the address set by external-controller: querying proxies, switching nodes, reading rules, viewing active connections, streaming live logs — the client's own GUI is, under the hood, just another consumer of this same API. Turn it on and pair it with a web panel, and you can manage a running core from any browser, independent of any specific client — this is exactly how a mihomo instance on a router or an unattended server instance gets managed.
How to enable it
external-controller: 127.0.0.1:9090
external-ui: ui
secret: "your-password"
external-controller is the API listen address; external-ui points to the panel's static file directory — once set up, visiting http://127.0.0.1:9090/ui opens the panel; secret is the access token, required for both panel login and API requests. For panel files, any of the community's ready-made release builds will do — metacubexd, zashboard, and yacd are all solid choices; download a release build and unzip it into your ui directory. You can also skip deploying local files entirely and just open a panel's hosted web page, entering your API address and secret — the browser will connect directly to your local API.
Common API endpoints
| Endpoint | Method | Purpose |
|---|---|---|
| /proxies | GET | Status of all proxies and proxy groups |
| /proxies/{name} | PUT | Switch the selected item in a select group |
| /proxies/{name}/delay | GET | Test latency for a given node |
| /rules | GET | The currently active rule list |
| /connections | GET / DELETE | View or clear active connections |
| /configs | GET / PATCH | Read or hot-reload the running config |
| /traffic · /logs · /memory | GET(WebSocket) | Live traffic rate, log, and memory streams |
| /version | GET | Core version info |
Every panel feature is built on top of these endpoints — and writing your own scripts against them is just as viable: scheduled speed tests with automatic switching, alerts based on connection count, feeding traffic data into a monitoring stack are all common uses. Verifying from the command line only takes one header:
curl -H "Authorization: Bearer your-password" http://127.0.0.1:9090/proxies
Security boundaries
Bind to 127.0.0.1 first, restricting access to the local machine only — this is both the default and the safest setup. If you genuinely need LAN access — say, managing a router's core from your phone — change the bind address to a LAN address, but you must set secret at the same time; an API with no token is effectively handing proxy control to anyone else on the same network. Under no circumstances should port 9090 be exposed to the public internet, and binding to 0.0.0.0 while relying on the firewall to catch everything is not a safe assumption to make.
Use a long, random string for secret, and restart the core after adding it to the config for it to take effect; it's passed via a request header, and the panel will remember it, so day-to-day use feels seamless. A panel's hosted web version connects directly from your browser to the API address you provide — the secret never passes through a third-party server — but it's still best practice to only do this on a trusted network, and to close the tab once you're done.
Related resources
Grab a client for your platform from the Download Center — Clash Plus is the top recommendation; for the main setup steps, see the setup tutorial; for how clients differ, see the client comparison; for a field-by-field breakdown of the YAML config, see the blog post "Clash Config File Explained"; for node selection and proxy verification on first connection, see "Clash First Connection Guide".