Xray API Node Auto-Switching: Advanced Automation Guide

Why automatic failover needs more than a ping

A long-lived Xray deployment can fail in several different ways. The process may still be running while one upstream server has stopped accepting traffic. A TCP connection may succeed while TLS negotiation, WebSocket establishment, or the actual destination request fails. A node can also remain reachable but become too slow for interactive work. For that reason, an automation script that only sends an ICMP ping is not a reliable node switcher.

A better design treats each outbound as a service with three separate properties: reachability, protocol health, and usable latency. Reachability asks whether the endpoint can be contacted. Protocol health checks whether the configured Xray transport can complete a connection. Usable latency measures whether the node can answer a small request within an acceptable time. The test should resemble real traffic without downloading a large page or exposing private application data.

Xray API automation is also not the same as blindly editing the main configuration file. The API can expose statistics, inspect runtime state, and alter selected routing behavior without restarting the whole process. That reduces disruption and makes rollback easier. However, the API must be enabled carefully: an unauthenticated management endpoint listening on a public interface is a serious security risk.

Before building failover logic, define the expected behavior. Decide how many consecutive failures should trigger a switch, how long the script should wait before retrying the failed node, whether existing connections may remain on the old route, and how the system should behave when every node is unhealthy. Clear policies prevent a short network fluctuation from causing constant route changes.

Separate Xray API statistics from routing control

Xray exposes several API services, and they do not all solve the same problem. The StatsService is useful for reading counters such as uplink and downlink traffic. It can tell you whether an outbound has carried data, but traffic counters alone do not prove that a node is currently healthy. A previously active node can continue to show old counters after the remote server becomes unavailable.

The HandlerService is associated with runtime handlers and outbound management. Depending on the Xray version and enabled features, it can be used to inspect or manage handlers without rewriting the entire static configuration. The LoggerService can help retrieve or control logging behavior, but logs are better used for diagnosis than as the only health signal. Log wording and availability may differ between versions.

For route selection, the important object is normally a named outbound or a balancer that routing rules can reference. A direct route should remain available as an explicit fallback where appropriate. Do not make the automation script depend on the current position of an outbound in a JSON array. Names are more stable than indexes, and a configuration review is much easier when every managed node has a clear tag such as node-sg-01, node-jp-01, or node-us-01.

Keep the control API on a loopback address such as 127.0.0.1 whenever the automation runs on the same host. If a remote controller is required, place the API behind a private management network, firewall rules, and an authenticated transport. Never expose an Xray API port directly to the public internet merely because the script is easier to configure that way.

One practical configuration pattern is to reserve a local API port and a dedicated management tag. In JSON terms, the relevant idea is "api": { "services": ["HandlerService", "StatsService"], "tag": "api" }, together with an inbound that accepts API traffic only on loopback. The exact inbound protocol and API fields should match the Xray version you have installed. Treat this as a design pattern, then verify the accepted schema against the version’s documentation before deploying it.

Design a stable node and balancer layout

Automatic switching is easiest when the configuration separates node definitions from route policy. Define each server as an outbound with a unique tag. Keep protocol, address, port, credentials, TLS settings, and transport settings inside that outbound. A route rule should then select a logical group or balancer rather than embedding a different server definition in every rule.

A simplified layout might contain outbounds named node-sg-01, node-jp-01, node-us-01, and direct. A balancer can reference the first three nodes, while the routing section sends selected traffic to that balancer. The group should not include direct unless bypass behavior is intentionally part of the policy. Mixing a direct route into a failover group can make a failed proxy appear healthy because requests quietly leave through the local connection.

If your Xray build supports balancer selectors or observatory-style health checks, use those native facilities where they meet your needs. Native selection can reduce custom code and may understand Xray’s internal outbound structure better than an external script. An external controller is still useful when you need custom HTTP probes, business-specific thresholds, notifications, maintenance windows, or integration with another monitoring system.

Use one source of truth for node names. If a provider subscription produces changing tags, create a stable mapping layer instead of hard-coding temporary names into the controller. Otherwise, the script may try to switch to an outbound that disappeared after a subscription update. A safe controller first fetches the current list of known handlers, compares it with its allowlist, and refuses to select an unknown tag.

Configuration reloads deserve the same care. Replacing the entire Xray configuration for every failure can interrupt all users, reset runtime state, and introduce syntax errors into an otherwise healthy service. Prefer a runtime API operation when it is supported and sufficient. If a full reload is unavoidable, write a temporary file, validate it, create a backup, and reload only after validation succeeds.

Build the health-check loop

The health checker should maintain state rather than making a decision from one request. For each node, store the last result, response time, consecutive failure count, consecutive success count, and the time of the last probe. A node becomes unhealthy after a threshold such as three consecutive failures. It becomes healthy again only after one or two successful checks, depending on how cautious you want recovery to be.

Use a short timeout, but do not make it unrealistically short. A timeout of two seconds may be reasonable for a local service check but too aggressive for a distant node during normal congestion. Choose a value from observed latency, then leave enough margin for transport overhead. Record whether a result was a connection refusal, a timeout, a TLS error, an HTTP status failure, or a response that exceeded the latency budget. Those details make later troubleshooting much faster.

Probe through the node you are testing, not around it. The controller needs to associate the test with a specific outbound. A common approach is to send a small request to a stable HTTPS endpoint through a dedicated routing rule or test mechanism. Avoid endpoints that redirect through regions, require complex authentication, or change behavior frequently. A small response with a predictable status code is easier to evaluate than a large public webpage.

Do not probe every node at exactly the same second. Add a small random delay, or distribute checks across the interval. Synchronized probes can create an artificial burst, especially when many Xray instances share the same provider. Also set a minimum interval between switches. This cooldown is essential because two nodes may alternate between barely passing and barely failing.

The selection score should reflect both health and purpose. For example, choose only nodes with a failure count below the threshold, then rank the remaining nodes by recent latency and configured priority. A node that is healthy but consistently five times slower should not always defeat a stable alternative. Conversely, do not abandon a preferred node after one slightly slow response.

Hands-on automation workflow

Start with a backup and a manual baseline. Export or copy the current Xray configuration, write down the active outbound tag, and confirm that the management API responds locally. Test one node at a time before enabling switching. If the basic API request does not work, automation will only hide the original configuration problem.

  1. Assign unique tags to every managed outbound and keep the names in an allowlist.
  2. Enable only the API services that the controller needs, and bind the API to loopback whenever possible.
  3. Confirm that the normal routing rule points to the intended balancer or selected outbound.
  4. Run a health probe against each candidate and store the result outside the Xray configuration.
  5. Require several consecutive failures before marking a node unhealthy.
  6. Select the best healthy candidate and compare it with the current active tag.
  7. Apply a runtime change only when the selected tag is different and the cooldown has expired.
  8. Send a log or notification containing the old tag, new tag, reason, and probe result.
  9. Continue testing the failed node in the background so recovery can be detected.

For a controller written in Python, Go, or JavaScript, keep API calls behind a small adapter. One function can list or inspect handlers, another can apply a selection change, and another can record failures. This separation prevents the health logic from becoming tightly coupled to HTTP request details. It also makes it easier to adapt if an Xray release changes an endpoint or request format.

Represent the policy in a separate configuration file. Useful fields include the candidate tags, probe URL, timeout, failure threshold, recovery threshold, cooldown, preferred order, and notification destination. Keep credentials and API secrets out of source control. If the controller runs as a service, give it a restricted operating-system account and read-only access to files that it does not need to modify.

When you first run the controller, use dry-run mode. It should report messages such as “would switch from node-jp-01 to node-sg-01 after three failures” without changing Xray. Compare those decisions with real observations for at least one maintenance window. Dry-run output often reveals incorrect tags, a probe that is routed through the wrong node, or a threshold that is too sensitive.

Understand safe switching and connection behavior

Changing the selected outbound does not necessarily move existing connections. Long-lived TCP sessions, WebSocket sessions, downloads, and application connection pools may continue using the old path until they close. New connections can use the new selection while old ones drain. This is normally preferable to forcibly terminating every session, but it means failover may appear incomplete for a short period.

If an application keeps retrying a broken connection, the client may need to reconnect before the new route is visible. For operational systems, define whether the controller is allowed to restart Xray or clear connections. In most cases, an automatic process restart should be a last resort. A restart can interrupt healthy users, discard useful diagnostic state, and create a second failure during a temporary provider outage.

Use hysteresis to prevent flapping. The failure threshold should be higher than one, and the recovery rule should be deliberately slower than the failure rule. For example, switch away after three failed probes, but require five successful probes over a longer period before restoring the preferred node. Add a cooldown such as sixty seconds between route changes. These values are examples, not universal defaults; measure your environment and tune them from logs.

Decide what happens when no candidate passes. A conservative policy is to keep the current route for a short grace period and report that all candidates are unhealthy. Another policy is to use a controlled direct route for only approved destinations. Do not silently fall back to direct traffic for sensitive applications just to make a health check appear green. Fail-open and fail-closed behavior should be an explicit security decision.

Add observability, logging, and alerts

A failover system without useful logs is difficult to trust. At minimum, record the timestamp, tested tag, probe destination, result, latency, failure category, active tag before the decision, selected tag after the decision, and controller version. Avoid logging full URLs that contain subscription tokens, usernames, or private query parameters. Logs should explain the decision without leaking credentials.

Track more than the number of switches. A useful dashboard includes probe success rate by node, median and high-percentile latency, time spent in an unhealthy state, switch count, and time since the last successful check. A node that fails once every hour may need a different response from a node that fails continuously. Repeated switching is often evidence of a poor threshold, a bad probe, or a route that is being tested through the wrong path.

Notifications should be rate-limited. Send an immediate alert for a switch away from the preferred node, but group repeated failures into a single incident. Send a recovery notice only after the recovery threshold has been satisfied. Include enough context for the operator to act: node tag, failure type, duration, and whether another candidate is active.

Test the monitoring path independently from the proxy path. If alerts use the same failed outbound as normal traffic, the notification may never arrive. A local log, a separate management network, or a different notification channel can provide a useful secondary signal.

Common failure modes and practical fixes

The API port is unreachable. Check whether Xray is listening on the expected address and whether the API inbound is bound to loopback. Verify that the controller is running on the same host or has a permitted management route. Do not immediately open the port publicly; first confirm the local binding and operating-system firewall.

The script switches to a nonexistent tag. This usually happens after a subscription update or a manual configuration edit. Fetch the current runtime list, compare it with the allowlist, and reject unknown tags. Stable naming or a provider-to-local tag mapping prevents this class of error.

Every node appears healthy even when browsing fails. The probe may be going through direct, through the currently active node, or through a route that does not match the traffic being evaluated. Make the test path explicit and verify it with logs. A successful local request is not proof that every managed outbound works.

The controller flaps between two nodes. Increase the failure threshold, add a cooldown, require multiple recovery probes, and rank candidates using a rolling latency window. Also inspect whether the test endpoint is rate-limiting requests or intermittently returning errors.

The API works after a restart but not during runtime. Review the Xray version, enabled API services, and the exact request format used by the controller. Runtime operations are version-sensitive. Keep a manual fallback procedure that restores the last known-good configuration, and test API changes in a staging instance before applying them to a shared gateway.

Security and maintenance checklist

Protect the API as a control plane, not as an ordinary application port. Bind it locally, restrict the service account, use firewall rules, and avoid embedding secrets in command-line arguments where other users may inspect the process list. If remote administration is necessary, use a private tunnel or management network and authenticate the caller.

Review the automation after every Xray upgrade. Confirm that the API services still start, the handler names are returned as expected, and the route change affects new connections. Keep the Xray configuration and controller policy under version control, but remove credentials before committing. A dated backup and a tested rollback command are more valuable than a complex recovery script that has never been executed.

Run failure drills. Stop one test node, introduce a controlled timeout, and confirm that the controller waits for the configured threshold, selects the intended replacement, logs the reason, and detects recovery later. Then test the case where all nodes fail. The system should produce a clear incident rather than silently sending protected traffic through an unintended route.

FAQ: Xray API node failover

Can I use a ping test as the only health check? No. Ping checks a network-layer response and may be blocked or prioritized differently from application traffic. Use a probe that exercises the relevant Xray outbound and combine it with a timeout and latency threshold.

Does changing the outbound immediately move all users? Usually it changes the route used by new connections. Existing sessions may remain on the old outbound until they close. This gradual behavior is often safer than restarting Xray, but applications with persistent connections may need their own reconnect logic.

Should direct traffic be the final fallback? Only when that behavior is acceptable for the destination and security policy. A direct fallback can preserve availability, but it can also bypass the privacy or access requirement that motivated the proxy. Make the choice explicit and alert when it occurs.

Is an external script always better than Xray’s native balancer tools? No. Native features are preferable when they provide the health checks and selection policy you need. An external script becomes useful for custom probes, business rules, notifications, maintenance schedules, and integration with existing monitoring. Keep the external layer small and let Xray continue handling the actual traffic path.

Final implementation principles

Reliable Xray failover is less about a single API command and more about disciplined system design. Name outbounds consistently, isolate the management API, test each node through the path it is supposed to serve, and keep health state outside the main configuration. Use consecutive-failure thresholds, recovery hysteresis, cooldowns, and a clear all-nodes-failed policy. Finally, log every decision and practice rollback before an outage makes experimentation expensive.

Start with one host and two nodes, run the controller in dry-run mode, and only then enable runtime switching. Once the behavior is predictable, extend it with latency ranking, alerts, and maintenance windows. This staged approach gives developers and network operators a transparent automation layer without turning every temporary network fluctuation into an emergency configuration change.