Almost every broken routing setup I have been asked to look at was broken by a single line. Not by a bad node, not by a flaky ISP, and almost never by the core itself. One rule in the wrong position, one tab character where spaces belonged, one port already taken by something else on the machine.
That is oddly good news, because it means troubleshooting is mostly a matter of knowing where to look. The configuration format is small enough that the same ten problems keep coming back, and each of them has a recognisable symptom you can spot without reading the whole file.
What follows is a list of those ten, written in the order I usually check them. Each one gets the symptom you would actually notice, the reason it happens, and a fix you can apply in a couple of minutes.
1. No MATCH Rule at the End of the List
Symptom. Most sites work, then something obscure — a game server, an API endpoint, a firmware updater — hangs forever or dies with a connection error. The connections log shows the request appearing and then nothing.
Cause. The rule list is walked from top to bottom and the first match wins. If nothing matches, there is no defined behaviour for that connection. MATCH is the catch-all that gives every unmatched request a home, and it has to be the last entry.
Fix. Put it at the bottom, always, no exceptions:
rules: - DOMAIN-SUFFIX,internal.example.com,DIRECT - GEOIP,CN,DIRECT - MATCH,PROXY
If you are unsure whether an unmatched request is your problem, temporarily set the final rule to MATCH,REJECT. Anything that suddenly breaks was falling through the bottom of your list.
2. A Broad DOMAIN-KEYWORD Shadowing Everything Below It
Symptom. A rule you wrote explicitly is being ignored. The rules viewer shows a completely different rule matching the connection.
Cause. Keyword rules are substring matches, and they are far greedier than people expect. A line like DOMAIN-KEYWORD,google,PROXY also catches googleapis.com, googlevideo.com, and any hostname that happens to contain those six letters anywhere. Placed near the top, it swallows every specific rule that comes after it.
Fix. Order rules from most specific to least specific. The reliable sequence is:
DOMAIN— exact hostname matches.DOMAIN-SUFFIX— a domain and everything under it.DOMAIN-KEYWORD— substring matches, kept as narrow as you can make them.IP-CIDRandGEOIP— address-based rules.MATCH— the final fallback.
Then use the rules viewer in your client to confirm which line actually fired. That view exists precisely so you do not have to guess.
3. IP Rules Without no-resolve
Symptom. Everything routes correctly, but DNS queries are going somewhere you did not intend, and there is a small delay before certain connections open.
Cause. When the engine meets an IP-CIDR or GEOIP rule and the request only carries a hostname, it has to resolve that hostname first to test the rule. That resolution happens before any routing decision has been made, which means the lookup can leave through a path you were not expecting.
Fix. Add no-resolve to address rules that sit above your domain rules. It tells the engine to skip that rule when only a hostname is available, rather than forcing a lookup:
- IP-CIDR,10.0.0.0/8,DIRECT,no-resolve - IP-CIDR,172.16.0.0/12,DIRECT,no-resolve - GEOIP,CN,DIRECT
The general shape to aim for: domain rules first, address rules with no-resolve next, and only then any address rule you genuinely want resolved.
4. Port 7890 or 9090 Is Already Taken
Symptom. The core refuses to start, or it starts but the dashboard shows nothing at all. The log mentions binding, listening, or an address already in use.
Cause. The mixed inbound port and the external controller port are both conventional defaults, which means anything else you have installed with the same defaults will fight over them. A second routing client left running in the background is the usual culprit; container runtimes and local development servers are the next most common.
Fix. Find out what holds the port, then either stop it or move yours.
# macOS / Linux lsof -i :7890 # Windows netstat -ano | findstr :7890
Changing your own ports is a two-line edit, and it is usually less disruptive than killing whatever else needed them:
mixed-port: 7893 external-controller: 127.0.0.1:9095
Remember that if you move the mixed port, anything you configured manually — a terminal HTTP_PROXY variable, a browser extension — has to be updated to match.
5. Confusing fake-ip With redir-host
Symptom. Domain rules stop matching under TUN mode, or certain applications — often games and video calls — behave strangely while ordinary browsing is fine.
Cause. These are two genuinely different DNS strategies and they are not interchangeable. In redir-host mode the engine resolves the hostname for real and then routes on the resulting address. In fake-ip mode it hands the application a placeholder address from a reserved pool, keeps the hostname in a lookup table, and recovers it when the connection arrives — so domain rules keep working even for traffic captured at the network layer.
Fix. For most desktop setups, fake-ip is the better default, with an exception list for anything that needs a real address:
dns:
enable: true
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
fake-ip-filter:
- "*.lan"
- "*.local"
- "time.*.com"
The trade-offs between the two modes are covered in more depth in the configuration notes that ship with most clash desktop clients, and it is worth reading them once before you change the setting on a machine you rely on. If you switch modes, flush the system DNS cache afterwards — stale placeholder addresses cause confusing symptoms that look like routing bugs.
6. TUN Mode On While the System Proxy Is Still Enabled
Symptom. Throughput drops noticeably, some connections loop, and the connections list shows the same destination appearing twice in quick succession.
Cause. These are two separate capture methods doing the same job. The system proxy asks cooperating applications to send their traffic to a local port. TUN mode installs a virtual network adapter and pulls traffic in below the application layer, whether the application cooperates or not. Run both and a browser’s traffic can be captured twice.
Fix. Pick one. TUN mode for system-wide coverage, system proxy for browser-only setups. Desktop clients such as Clash Verge Rev put both switches on the same settings page, which makes it easy to see at a glance that you have enabled two things that were meant to be alternatives. Turn one off, restart the core, and check whether the duplicate entries in the connections log disappear.
7. The Subscription Never Actually Updates
Symptom. Nodes that worked last month time out. Latency tests fail across the board, even though a fresh import of the same URL works instantly.
Cause. Node endpoints change. If the profile was imported once and no update interval was ever configured, the client keeps serving you a snapshot of whatever the provider published on the day you added it.
Fix. Set an interval on the profile itself. Something in the range of a few hours to a day is sensible for most providers; refreshing every few minutes gains you nothing and may get you rate-limited.
proxy-providers:
main:
type: http
url: "https://example.com/your-subscription"
interval: 21600
path: ./providers/main.yaml
health-check:
enable: true
url: http://www.gstatic.com/generate_204
interval: 600
After any update, run a latency test manually rather than assuming the refresh worked. A provider that returns an error page instead of a config will leave you with an empty node list and no obvious warning.
8. url-test Intervals Set Far Too Aggressively
Symptom. Connections drop mid-transfer. Long downloads restart. Video calls stutter at regular intervals rather than randomly.
Cause. An automatic policy group re-tests its members on a timer and switches to whichever answered fastest. Set that timer low enough and the group changes its mind constantly, breaking any connection that was already open through the previous node.
Fix. Give the test room to breathe, and add a tolerance so that trivial differences do not trigger a switch:
proxy-groups:
- name: AUTO
type: url-test
proxies: [NodeA, NodeB, NodeC]
url: http://www.gstatic.com/generate_204
interval: 300
tolerance: 100
The tolerance value, in milliseconds, is the part people forget. Without it, a node that is 5 ms faster on one sample is enough to cause a switch. With it, the group only moves when the difference is genuinely meaningful.
9. YAML Indentation, Tabs and Quoting
Symptom. The config refuses to load and the error message points at a line number that looks completely fine.
Cause. YAML is whitespace-significant and it does not accept tab characters for indentation at all. Editors that convert spaces to tabs, or that mix the two after a copy-paste, produce files that look correct and parse as garbage. The reported line number is often where the parser gave up, not where the mistake is.
Fix. Work through this list in order:
- Configure your editor to insert spaces, never tabs, and show whitespace characters.
- Use two spaces per level and keep it consistent throughout the file.
- Quote any value containing
:,#,@, or a leading wildcard — passwords and filter patterns are the usual offenders. - Check the lines above the reported error as well as the line itself.
- Validate the file before loading it, rather than after.
python3 -c "import yaml,sys; yaml.safe_load(open(sys.argv[1]))" config.yaml
Silence means the file parsed. Any output tells you exactly which construct the parser choked on.
10. Private and LAN Ranges Never Bypassed
Symptom. The network printer disappears. A NAS stops mounting. The router’s admin page will not load. Everything on the public internet is fine.
Cause. Local addresses are being sent to a remote node, which naturally has no idea what 192.168.1.50 means on your network. Under TUN mode this bites harder, because local traffic is captured too.
Fix. Bypass the private ranges explicitly, near the top of the rule list:
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve - IP-CIDR,10.0.0.0/8,DIRECT,no-resolve - IP-CIDR,172.16.0.0/12,DIRECT,no-resolve - IP-CIDR,127.0.0.0/8,DIRECT,no-resolve - IP-CIDR6,fc00::/7,DIRECT,no-resolve - DOMAIN-SUFFIX,local,DIRECT - DOMAIN-SUFFIX,lan,DIRECT
If your devices are announced over mDNS, add the .local suffix to your DNS filter list as well, or discovery will still fail even though the routing rule is correct.
A Two-Minute Triage Table
When something breaks and you have no idea which of the ten it is, start from the symptom rather than the config file.
What you observeCheck firstCore will not start at allPort conflict, then YAML syntaxOne specific site failsRule order and keyword shadowingEverything is slow, nothing failsurl-test interval, then double captureLocal devices unreachablePrivate range bypass rulesAll nodes time outStale subscriptionDomain rules ignored under TUNDNS mode
Questions That Come Up Often
How do I tell which rule matched a connection?
Open the connections view in your client while the request is live. It lists the destination, the rule that fired and the node that was chosen. If the rule shown is not the one you expected, you have an ordering problem — go back to mistake two.
Should I edit the config file or use the GUI?
Use the interface for switching nodes, toggling modes and running latency tests. Edit the file for anything structural — rule lists, DNS settings, policy group definitions. Keep your hand-written additions in a separate override or merge file so a subscription refresh cannot overwrite them.
Why does a rule work in one mode but not another?
Because the metadata available to the engine differs. Under a system proxy the hostname usually arrives intact. Under TUN mode, whether it does depends on your DNS mode, which is why fake-ip matters so much for domain-based rules.
Is there a safe way to test changes?
Copy the working config, edit the copy, validate it with a YAML parser, then load it as a second profile. Keep the known-good one selectable so a single click gets you back to a working state if the new one misbehaves.
Wrapping Up
Nine times out of ten, a broken configuration is one of these ten problems. They cluster because the format encourages the same shortcuts from everyone: a keyword rule added in a hurry, an update interval never set, a default port assumed to be free.
The habits that prevent most of them are unglamorous. Validate before you load. Order rules from specific to general. Bypass your own network explicitly. Change one thing at a time and read the connections log rather than guessing. Keep a config you know works, so that recovery is always a click away.
Do that consistently and configuration stops being the fragile part of the setup. It becomes the part you barely think about — which is exactly what a routing engine should be once it is properly set up.
