Where does the millisecond figure next to each node come from? Why is it so far off what ping reports? And why does the automatic group keep switching?
This explains the whole business of latency testing.
It is not a ping — it is a complete HTTP request
The steps Clash goes through:
That is fundamentally different from ping:
| ping | Clash latency test | |
|---|---|---|
| Protocol | ICMP | TCP + TLS + HTTP |
| Path measured | you → node | you → node → target site → back |
| Includes handshake | No | Yes |
| Reflects | Network distance | Real usage experience |
So Clash showing 180 ms while ping says 60 ms is entirely normal — the difference is handshake cost plus the leg from the node to the target site.
unified-delay: making protocols comparable
Handshake cost varies a lot between protocols. Trojan (TLS) inherently costs one extra round trip compared with Shadowsocks (no TLS), and Hysteria2 on QUIC is different again.
Comparing raw totals is unfair to some protocols. Turn on:
unified-delay: trueThe core measures the handshake twice, subtracts the protocol's inherent cost, and keeps only the comparable part.
The three parameters that matter
- name: "♻️ Auto"
type: url-test
proxies: [...]
url: "http://www.gstatic.com/generate_204"
interval: 300
tolerance: 50
lazy: true
timeout: 5000
max-failed-times: 5interval: how often to test
| Value | Effect | Suits |
|---|---|---|
| 60 | Very responsive switching | Wildly variable node quality; but constantly bothers every node |
| 300 | The balance point | The everyday recommendation |
| 600–900 | Very quiet | Stable nodes you would rather leave alone |
| 1800+ | Essentially static | A dead node goes unnoticed for half an hour |
tolerance: the most important and most overlooked
Tolerance in milliseconds. It means: a new node has to be this much faster before switching is worth it.
Suggested values:
- Node latencies mostly under 100 ms →
tolerance: 30 - Mostly 100–300 ms →
tolerance: 50 - Wildly variable →
tolerance: 100
lazy: skip testing while idle
lazy: trueWith this on, the group skips testing when no traffic is currently going through it.
Benefits: saves power, saves data, avoids pointless connections. Particularly noticeable on laptops and phones.
Cost: when you come back to the group, the first choice may be based on stale data until one test round completes.
Turn it on for everyday use.
timeout and max-failed-times
timeout: 5000 # timeout for one test, in milliseconds
max-failed-times: 5 # consecutive failures before the node is judged deadToo short a timeout misjudges slow nodes as unusable; too long makes each round drag. 5000 ms is a reasonable value.
Choosing a test URL
The default http://www.gstatic.com/generate_204 has two properties: it returns an empty 204 (almost no data transferred) and it is deployed on a global CDN.
Alternatives:
| URL | Character |
|---|---|
http://www.gstatic.com/generate_204 | Default, lightweight |
http://cp.cloudflare.com/generate_204 | Cloudflare, broad coverage |
http://connectivitycheck.gstatic.com/generate_204 | Same Google family |
https://www.youtube.com/generate_204 | Tests YouTube reachability directly |
http://connectivitycheck.platform.hicloud.com/generate_204 | Reachable where the others are not |
Low latency ≠ high speed
This is the point that most needs clearing up.
url-test measures latency only. Whether a node can carry heavy traffic can only be found by actually downloading something.
If your main use is video and large files, do not rely entirely on the automatic group — measure a few nodes yourself and pin the winner with a select group.
A useful arrangement: tiered testing
proxy-groups:
# main group: low tolerance, tested often, chasing responsiveness
- name: "⚡ Responsive"
type: url-test
include-all: true
filter: "(?i)HK|Hong|JP|Japan"
url: "http://www.gstatic.com/generate_204"
interval: 300
tolerance: 30
lazy: true
# heavy traffic: high tolerance, few switches, no interrupted downloads
- name: "📦 Bulk transfer"
type: fallback
include-all: true
filter: "(?i)IPLC|dedicated|premium"
interval: 600
# insurance: anything that works
- name: "🛡 Backstop"
type: fallback
include-all: true
interval: 900Why bulk transfer uses fallback rather than url-test: while downloading or watching video, the worst thing is switching nodes mid-stream — the connection breaks and progress is lost. fallback only changes when the current node genuinely dies, which is far steadier.
Testing by hand, properly
The lightning icon in the interface runs one round for the current group. Some caveats:
Triggering a test through the API
Useful when scripting:
# test one node
curl "http://127.0.0.1:9090/proxies/HK-01/delay?timeout=5000&url=http%3A%2F%2Fwww.gstatic.com%2Fgenerate_204" \
-H "Authorization: Bearer your-secret"
# test an entire group
curl "http://127.0.0.1:9090/group/%E2%99%BB%EF%B8%8F%20Auto/delay?timeout=5000&url=..." \
-H "Authorization: Bearer your-secret"Group names containing spaces or emoji need URL encoding.
In short
- It measures a full round trip to a target site through the node, which is not the same as ping
- Always set tolerance; 50 is a good starting point, and without it the group flip-flops
- Use 300 for interval; anything much shorter can trip a provider's risk controls
- Leave lazy on to save power and data
- Enable
unified-delayso nodes on different protocols are comparable - Latency says nothing about bandwidth — use fallback rather than url-test for heavy transfers
Related: the five proxy-group types and node naming and automatic grouping.
Related docs
A Clash / Mihomo config taken apart from top to bottom — ports, mode, DNS, proxies, proxy-groups, rules and rule-providers — with a minimal working config you can paste straight in.
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.