Why automatic Clash node switching matters
Manual proxy switching looks harmless when you are testing a profile at home: open the Clash client, click another node, refresh the page, and continue working. The cost becomes much higher when Clash runs beside a developer workstation, a build server, a monitoring process, or a long-lived API client. A node can become slow without going fully offline, a provider may return a successful TCP connection while upstream requests time out, or a relay may pass a small health request but fail during a long streaming response. In each case, a person who changes the proxy by hand is acting as an unreliable control plane.
This guide explains how to use the Clash-compatible external controller API to observe proxy groups, measure candidate nodes, and automate failover. The examples apply to Clash clients and Mihomo-based clients that expose an API such as 127.0.0.1:9090. Exact menu names differ between Clash Verge, Clash Verge Rev, Mihomo, and other clients, but the operating ideas remain the same: keep the controller private, identify the correct proxy group, test more than one signal, apply sensible latency thresholds, and make a switch only when the evidence justifies it.
Automatic selection is not the same as choosing the node with the smallest single ping. A node that responds in 80 milliseconds may have poor throughput, unstable TLS, or a congested route to the actual service you use. A practical failover loop combines availability, response time, consecutive failures, cooldowns, and a clear recovery policy. It should also fail safely when the API is unavailable instead of repeatedly changing configuration and making an incident harder to understand.
Understand the Clash controller API and proxy groups
The external controller is an HTTP interface served by the Clash core. A typical configuration declares an address and, optionally, an authentication secret. Many local installations bind to loopback so that only the same machine can reach the API. A remote bind address is convenient for administration but increases the attack surface considerably; never expose an unauthenticated controller directly to the public internet.
The API is useful because it separates observation from interaction. You can read the complete proxy tree, inspect the currently selected member of a group, measure a node through the core, and update the group without opening the graphical client. A common workflow looks like this:
- Discover groups: request the proxy list and find the group that your rules actually reference, such as
PROXY,Auto, or a provider-specific selector. - Inspect current state: read the group’s current
nowvalue before changing anything. - Test candidates: call the delay endpoint for individual proxies with a short, realistic URL.
- Switch deliberately: send a PUT request to the group endpoint only after your selection logic has applied thresholds and exclusions.
- Verify the result: read the group again and inspect Clash connections or logs to confirm that new traffic uses the expected route.
The exact endpoint names can vary by core version, so verify them against the API documentation or the controller behavior of your installed build. In many Mihomo-compatible deployments, the proxy inventory is exposed under an endpoint similar to /proxies, individual delay checks use a path similar to /proxies/{name}/delay, and group selection uses a PUT request to /proxies/{group}. Names containing spaces, slashes, or non-ASCII characters must be URL-encoded. Do not construct paths by simple string concatenation without encoding the proxy name.
Authentication is part of the design, not an optional finishing touch. If the controller requires a secret, send it through the expected authorization header and keep it outside source control. Prefer an environment variable, an operating-system secret store, or a restricted service configuration file. A controller token can change routing for every application using the client; leaking it is more serious than leaking a harmless read-only dashboard URL.
Design a group that can actually fail over
Automation can only choose among members of a group. If your rules send traffic to Streaming but your script changes PROXY, the API call may succeed while user traffic remains unaffected. Start by tracing one real connection in the Clash logs and identify the rule target. Then confirm that the target is a selector, URL-test group, fallback group, or another type that accepts the selection operation supported by your core.
Separate stable policy from experimental automation. Keep a manual selector for emergency use, and create a dedicated automatic group containing only the nodes you trust for the task. Exclude expired subscriptions, regional nodes that violate your application requirements, and proxies with names that match maintenance or backup labels. A smaller candidate set produces more meaningful measurements and prevents a script from selecting an attractive but unsuitable endpoint.
Do not confuse a URL-test group with a complete incident-management system. Built-in health checks may update a group according to the client’s own interval and tolerance, while your script may need authentication checks, service-specific URLs, or notification hooks. Use one mechanism as the primary owner of a group. If both Clash’s internal scheduler and an external script constantly write to the same selector, the result can oscillate and become difficult to audit.
Build health checks that reflect real traffic
A health check should answer a specific question. A lightweight request to a stable HTTPS endpoint asks whether a candidate can resolve a hostname, establish a connection, complete TLS negotiation, and receive a response through the selected node. It does not prove that every website or API will work. Choose a URL that is permitted in your environment, responds consistently, and resembles the traffic you need to protect. If you operate an internal service, a small authenticated health endpoint is often more useful than a random public URL.
Latency needs context. A single measurement is noisy because of queueing, DNS behavior, temporary congestion, and server-side scheduling. Take several samples, discard obvious failures, and use a median or a trimmed average rather than blindly trusting the minimum. A useful policy might reject candidates with no successful sample, reject a median above 800 milliseconds for interactive work, and prefer a candidate below 350 milliseconds when several nodes are healthy. These are starting points, not universal truths. A remote region may be stable at 500 milliseconds while a nearby node may be fast but unreliable.
Use consecutive failures to avoid switching because of one transient packet loss. For example, mark a node as degraded after two failed probes and eligible for failover after three failures separated by the normal check interval. Conversely, require two or three successful checks before restoring a recovered node. This hysteresis prevents a node from bouncing between healthy and unhealthy states at the boundary of a threshold.
- Availability threshold: require at least two successful responses in a small sample window.
- Latency threshold: define a maximum acceptable median and an optional warning threshold.
- Failure count: switch only after consecutive failures, not after one timeout.
- Cooldown: wait after a switch before evaluating another candidate.
- Recovery rule: do not immediately switch back to a recovered node unless it is materially better.
- Exclusion rule: skip nodes that fail policy checks even if their latency is excellent.
Measure the right layer. A Clash delay endpoint may perform a request through the proxy, but the result can still differ from your application because the application uses another hostname, HTTP/2, WebSocket, UDP, a custom certificate store, or a longer timeout. For important services, combine controller-level checks with a small end-to-end probe from the process that actually consumes the proxy. A server-side script should not assume that a desktop browser’s success proves that a headless worker has identical DNS, environment variables, or certificate trust.
Practical implementation: inspect, test, switch, and verify
Before writing an automated loop, perform one complete manual cycle. Enable the controller in the Clash client, confirm that it listens only where intended, and determine whether authentication is required. Then list the proxy groups and record the exact group name. This avoids a common error where a script targets a visually similar group that is not connected to any active rule.
- Discover the active group. Request the proxy inventory and inspect the group’s type, current selection, and available members. Save a sanitized response for troubleshooting, but remove secrets and private hostnames before sharing it.
- Test one candidate. Use a short timeout and a permitted HTTPS URL. Confirm that the response status and measured delay are meaningful instead of treating any HTTP response as proof that the application will work.
- Test the candidate set. Run checks sequentially or with a conservative concurrency limit. Excessive parallel probes can overload the local core, the provider, or the destination and create false failures.
- Rank healthy members. Filter out failures and policy violations first. Then rank by median latency, recent stability, region, or a weighted score that matches your workload.
- Apply a switch. Change the group only when the best candidate beats the current node by a useful margin, or when the current node has crossed the failure threshold.
- Confirm routing. Read the selected value from the controller, generate a test request from the real application environment, and inspect Connections or logs for the final rule and outbound proxy.
A conceptual request sequence might resemble the following. Replace the address, group name, candidate name, URL, and token according to your own installation. These commands are deliberately illustrative because endpoint details and authentication conventions can differ between Clash cores.
export CLASH_API="http://127.0.0.1:9090"
export CLASH_SECRET="replace-with-a-secret"
curl -H "Authorization: Bearer ${CLASH_SECRET}" \
"${CLASH_API}/proxies"
curl -G -H "Authorization: Bearer ${CLASH_SECRET}" \
--data-urlencode "url=https://example.com/generate_204" \
--data-urlencode "timeout=5000" \
"${CLASH_API}/proxies/Node%20A/delay"
curl -X PUT \
-H "Authorization: Bearer ${CLASH_SECRET}" \
-H "Content-Type: application/json" \
-d '{"name":"Node A"}' \
"${CLASH_API}/proxies/Auto"
Do not copy these commands into production without checking the response schema. Some versions expect a different JSON body, a different authorization format, or a different delay endpoint. First make a read-only request, then test a single manual switch, and only afterward add a scheduler. A dry-run mode that prints the proposed selection without changing Clash is valuable during the first day of observation.
For a scripted failover service, keep state in memory or in a small local file: last selected node, failure count per node, last switch time, and recent measurements. Add a minimum switch interval so a slow destination cannot trigger repeated changes. Use exponential backoff when the controller itself returns connection errors. If the controller is down, preserve the existing route and alert an operator rather than guessing or rewriting configuration files while the client may be running.
Run the loop with an explicit service identity and minimal permissions. On Linux, a systemd service can receive the controller token through a protected environment file; on Windows, Task Scheduler or a small service can run the script under a restricted account. Containers need particular care: 127.0.0.1 inside a container points to the container, not necessarily the host running Clash. Use a controlled host gateway or a private management network, and firewall the controller so that only the automation process can reach it.
Debugging failover without creating a new outage
Start with the controller response, not the user interface. A successful API status code only means that the core accepted the request. Read the returned selected name and compare it with the actual connection log. If the selected group changes but a connection still uses another proxy, inspect the rule chain, nested groups, process-level proxy variables, and whether the application opened a long-lived connection before the switch.
DNS is a frequent source of misleading results. A node may pass a URL test while the application resolves a hostname through a different resolver, receives an unsuitable address, or uses fake-IP mappings that do not match its network mode. Compare the DNS mode, nameserver behavior, and logs during a failure. Avoid changing DNS, TUN, routing mode, and node selection all at once; change one variable, reproduce the symptom, and preserve the evidence.
Authentication errors require a separate branch of diagnosis. If the controller returns unauthorized, forbidden, or malformed-request responses, verify the token, header spelling, endpoint path, and clock only after confirming the API version. Do not respond by disabling authentication. If the script works locally but fails from a server, check the server’s route to the controller, container networking, firewall rules, and environment-variable availability.
Monitor more than average latency. Record success rate, timeout rate, switch count, time spent on each node, and the reason for every decision. An alert such as “switched from Node A to Node B after three failures” is actionable; an alert that says only “proxy unhealthy” is not. Keep logs free of subscription URLs, controller secrets, authorization headers, and full request paths that might contain tokens.
Oscillation usually has a recognizable pattern: two nodes alternate, switch timestamps are close together, and both sit near the threshold. Fix it with hysteresis, a cooldown, a meaningful improvement margin, and a recovery hold period. If all candidates fail, select a defined fallback behavior. Depending on your policy, that may be keeping the current node, using a direct route for approved domains, or stopping a dependent job with a clear error. Automatic failover should not silently route sensitive traffic somewhere unexpected.
Frequently asked questions
Should I expose the Clash API to another machine?
Only when there is a clear operational reason. A loopback controller is safer for a script running on the same host. If a remote server must manage the client, restrict the bind address with a firewall or private network, require authentication, and allow only the automation host to connect. Avoid publishing the controller through a public reverse proxy unless you understand authentication, TLS, access control, and audit requirements.
Is the node with the lowest delay always the best choice?
No. Delay measures only the behavior of a particular probe at a particular moment. Throughput, packet loss, TLS compatibility, destination geography, UDP support, and long-session stability can matter more. Use delay as one signal, combine it with consecutive success results, and validate the route with a small request from the real workload.
How often should an automatic script switch nodes?
There is no universal interval, but switching every few seconds is usually too aggressive. Start with checks spaced far enough apart to observe real failures, require multiple consecutive failures, and enforce a cooldown after every switch. Interactive traffic may tolerate a faster response than a build server or a long-lived streaming API; tune the policy to the cost of interruption.
Why does a copied API example return an error?
Clash-compatible clients do not always expose identical endpoint paths, request bodies, or authentication formats. Check the API behavior of the core bundled with your client, URL-encode group and node names, and inspect the HTTP status plus response body. Test read-only discovery first, then perform one controlled switch before enabling a scheduler.
Compared with desktop VPN tools that offer only a single global reconnect button, or simple URL-test utilities that chase the fastest ping without understanding your active rule group, a Clash API workflow gives you observable selection, policy-aware candidates, authentication, cooldowns, and logs that explain each decision. That makes it a better fit for developer machines and servers where an unnoticed route change can interrupt a build or expose traffic to the wrong path. If you want to reproduce this controlled failover setup across your own platforms, start with a Clash client that exposes the configuration and monitoring controls described above.