Getting Started 12-minute read

Ten V2Ray Questions Answered for Beginners: Subscriptions, Cores, Protocols, and Client Selection

Build a practical Xray node failover workflow with native gRPC APIs and automation scripts. The guide explains how health probes, traffic counters, latency thresholds, outbound handlers, and fallback rules work together, with reusable JSON examples for engineers managing self-hosted proxy infrastructure.

A node that works during a quick manual test can still fail minutes later because of packet loss, upstream congestion, DNS problems, or an expired server-side configuration. Xray provides two useful building blocks for a practical failover workflow: native gRPC APIs for reading runtime statistics and changing handlers, plus observatory and balancer features for probing and selecting outbounds. This guide combines them into a cautious automation design that can monitor nodes, apply latency and error thresholds, and switch traffic without rewriting the entire client configuration.

Article summary

This guide is for engineers managing self-hosted Xray infrastructure or a controlled fleet of client configurations. It explains the relationship between observatory probes, StatsService counters, outbound tags, balancer rules, and HandlerService operations, then builds a reusable health-check script with concrete ports, thresholds, JSON fragments, and rollback precautions.

Start with the failure model, not the switching script

Automatic node switching is often described as “test the latency and select the fastest server.” That description is incomplete. A low probe latency does not prove that authentication, DNS resolution, long-lived streams, or the target application will work. Conversely, a node that misses one probe may still be usable when the probe destination is temporarily slow. A reliable workflow therefore separates observation from action: collect several signals, classify the node, and switch only after a clear failure condition persists.

In an Xray deployment, the local application sends traffic to an inbound, the routing engine chooses an outbound or balancer, and the selected outbound connects to the remote server. Each important outbound should have a stable and unique tag such as node-a, node-b, or node-c. Avoid using display names that change whenever a subscription is refreshed. Automation should refer to tags, not to visible labels in v2rayN, v2rayNG, or another graphical client.

Probe targetObservatory resultStats countersPolicy decisionOutbound switch

The observatory is responsible for active probing. It can test selected outbounds against a configured probe URL and expose results to the balancer. The StatsService is different: it reports counters such as uplink and downlink values for inbounds and outbounds when statistics are enabled. Counters tell you whether traffic is actually flowing and whether an outbound is accumulating failures in your surrounding monitoring system; they do not themselves measure round-trip latency.

A useful design keeps three layers distinct:

Operational conclusion: switching is a state machine

Do not switch after one slow result. Require consecutive failures, add a cooldown, and remember the active node so that a monitoring restart cannot repeatedly flip between two marginal outbounds.

Understand the Xray API services and statistics

Xray’s API is normally exposed through a local gRPC inbound. A common management endpoint is 127.0.0.1:10085, although the port is entirely configurable. The API inbound is routed to an api outbound tag, and the configuration must enable the services that the automation will call. For a health-check controller, StatsService is the minimum useful service; HandlerService is required if the script will add, remove, or alter runtime handlers.

API inbound

Protocol
dokodemo-door
Listen
127.0.0.1
Port
10085
Target
127.0.0.1:10085

Keep the management API on loopback unless a protected management network is required.

Enabled services

Statistics
StatsService
Runtime changes
HandlerService
Probe selection
Observatory
Control tags
node-a, node-b

Service names must match the gRPC API clients and the running Xray build.

The statistics section can enable inbound and outbound counters. For outbound monitoring, use a tag pattern that matches the node tags you want to inspect. The exact counter names returned by the API include directions such as uplink and downlink, so a controller should query by a stable pattern and treat a missing counter as “not observed” rather than immediately as a hard failure.

{
  "api": {
    "services": [
      "HandlerService",
      "StatsService"
    ],
    "tag": "api"
  },
  "stats": {},
  "policy": {
    "levels": {
      "0": {
        "statsUserUplink": true,
        "statsUserDownlink": true,
        "statsInboundUplink": true,
        "statsInboundDownlink": true,
        "statsOutboundUplink": true,
        "statsOutboundDownlink": true
      }
    }
  }
}

With a compatible Xray API client, the StatsService method commonly used for this task is QueryStats. A request can query a regular expression such as ^outbound>>>node-a>>>traffic>>>.*. The returned values are cumulative byte counters. To calculate a rate, store the previous value and divide the difference by the elapsed seconds. A counter that remains unchanged is not automatically a failure: the node may simply have no traffic during that interval.

Latency is not obtained by subtracting two StatsService counters. Use observatory data when the running Xray version and API client expose the necessary method, or run an external probe through the same outbound path. External probes should use a controlled HTTPS endpoint with a short response, a timeout between 3 and 8 seconds, and a fixed request interval. Do not probe a large webpage every few seconds; that creates unnecessary traffic and can make the monitoring result less representative.

Give every outbound a predictable role

Failover becomes safer when the routing configuration already contains the primary and backup outbounds. Instead of generating a new full Xray configuration for every incident, define stable tags and let the balancer or a controlled routing change select among them. A direct outbound should remain separate from proxy candidates so that a failed proxy does not accidentally turn a privacy-sensitive route into a direct connection.

Observatory evaluates selected outbounds and the balancer chooses an available candidate. This reduces configuration churn and is well suited to several stable node tags.

Suitable for: routine failover and multi-node pools

The controller changes which outbound tag a rule targets. It is explicit and easy to audit, but runtime routing changes require careful API handling and a clear rollback plan.

Suitable for: one primary and one emergency route

HandlerService alters or replaces a runtime outbound. This can help rotate server parameters, but it has a larger blast radius than selecting an existing tagged node.

Suitable for: controlled credential or endpoint rotation

A typical balancer references tags rather than duplicating the complete outbound definitions. The routing rule then sends selected traffic to the balancer tag. The precise observatory and balancer fields can vary between Xray versions, so validate the schema against the version installed on the server before deploying automation. A configuration that parses successfully but references a misspelled tag may silently produce the wrong routing result.

{
  "outbounds": [
    {
      "protocol": "vless",
      "tag": "node-a",
      "settings": {
        "vnext": [
          {
            "address": "edge-a.example.net",
            "port": 443,
            "users": [
              {
                "id": "00000000-0000-0000-0000-000000000000",
                "encryption": "none"
              }
            ]
          }
        ]
      },
      "streamSettings": {
        "network": "tcp",
        "security": "tls"
      }
    },
    {
      "protocol": "vless",
      "tag": "node-b",
      "settings": {
        "vnext": [
          {
            "address": "edge-b.example.net",
            "port": 443,
            "users": [
              {
                "id": "00000000-0000-0000-0000-000000000000",
                "encryption": "none"
              }
            ]
          }
        ]
      },
      "streamSettings": {
        "network": "tcp",
        "security": "tls"
      }
    }
  ],
  "routing": {
    "balancers": [
      {
        "tag": "proxy-pool",
        "selector": [
          "node-a",
          "node-b"
        ]
      }
    ],
    "rules": [
      {
        "type": "field",
        "network": "tcp,udp",
        "balancerTag": "proxy-pool"
      }
    ]
  }
}

The example is intentionally a structural starting point rather than a complete production file. Real VLESS deployments may require Reality, WebSocket, gRPC transport, a specific server name, flow setting, or additional security fields. Keep those details exactly as supplied by the server operator or subscription. The failover controller should not “simplify” a working transport when it changes the selected node.

Build the health-check and switching workflow

Before writing code, define what “healthy” means. For a small two-node pool, a reasonable initial policy is: probe every 15 seconds, mark a node degraded after three consecutive probe failures, reject it when latency exceeds 1,500 milliseconds for three consecutive samples, and require five successful samples before restoring it to the preferred pool. These values are starting points, not universal truths. A transcontinental route may normally exceed 300 milliseconds, while a local server may be expected to remain below 100 milliseconds.

  1. Reserve API access

    Bind the API to 127.0.0.1:10085, enable StatsService and HandlerService, and restrict access with a host firewall if the API must be reached from a monitoring host.

  2. Stabilize tags

    Assign immutable tags such as node-a, node-b, and proxy-pool. Record the current primary node in a state file outside the generated subscription output.

  3. Collect samples

    Every 15 seconds, record probe success, latency, timestamp, and the relevant StatsService counters. Keep at least the last 10 samples per node for debugging and trend review.

  4. Apply thresholds

    Switch only after three consecutive failures or three consecutive latency violations. Exclude a degraded node for a 60-second cooldown so it cannot immediately win the next selection.

  5. Verify the result

    After switching, run a real request through the selected path and confirm that the new outbound counter increases. If verification fails, move to the next candidate or raise an alert instead of cycling indefinitely.

The controller can be implemented in Python, Go, or a shell wrapper around a gRPC client. A useful pseudocode sequence looks like this:

for node in candidates:
    sample = probe(node, timeout=5)
    counters = query_stats("outbound>>>" + node + ">>>traffic>>>.*")
    history[node].append(sample)

current = read_state("active-node")
if consecutive_failures(current) >= 3:
    target = best_healthy_candidate(
        max_latency_ms=1500,
        min_success_samples=3,
        exclude_for_seconds=60
    )
    if target and target != current:
        switch_to(target)
        verify_forwarding(target)
        write_state("active-node", target)

If the controller uses HandlerService, treat the API request as a runtime operation, not as a permanent configuration edit. A successful gRPC response means the handler operation was accepted; it does not prove that the remote node is reachable. Always follow the operation with a real probe and inspect Xray logs. If the client or server is managed by a subscription, a later subscription update may regenerate the configuration and discard runtime changes. In that environment, prefer stable balancer selection or update the source configuration rather than relying on a temporary alteration.

Conservative policy

Interval
30 seconds
Failure count
4 samples
Latency limit
2,000 ms
Cooldown
180 seconds

Best when switching itself is disruptive or the route is naturally variable.

Responsive policy

Interval
15 seconds
Failure count
3 samples
Latency limit
1,500 ms
Cooldown
60 seconds

Useful for interactive services, provided the probe endpoint is stable.

Prevent flapping, false positives, and unsafe changes

Flapping occurs when two nodes alternate between barely passing and barely failing. The usual causes are a threshold placed too close to normal performance, a probe interval shorter than the route’s natural variation, or a recovery rule that is too eager. Use separate failure and recovery conditions: fail after three bad samples, but recover only after five good samples. Add a minimum residence time, such as 120 seconds, before another automatic switch is allowed.

Do not use only latency. Combine at least one availability signal with one performance signal. For example, require a successful probe and a latency below the limit; use StatsService to confirm that traffic counters increase after activation. If the node is idle, the absence of counter growth should remain neutral. If the node is active and counters stop increasing while applications report timeouts, classify it differently from an unused node.

SignalWhat it tells youWhat it cannot proveSuggested response
Probe timeoutThe selected test did not complete within the deadlineThat every destination is unreachableCount consecutive failures and retry
High latencyThe test path is slower than the policy limitThat throughput is equally poorDegrade after repeated samples
Rising uplink counterTraffic is entering the outboundThat the remote response returns successfullyCheck downlink and application results too
No counter changeNo observed traffic during the windowThat the node is deadKeep the result neutral when idle
API unavailableThe control plane cannot be queriedThat all proxy traffic has failedFreeze switching and alert the operator

Protect the control plane as carefully as the data plane. Never expose an unauthenticated Xray API directly to the public network. Keep it on loopback where possible, use an authenticated and encrypted management channel when remote access is unavoidable, and run the controller with the minimum operating-system privileges required. Log every decision with the node tag, old and new state, measured latency, failure count, and reason. These records make it possible to distinguish a genuine outage from a faulty probe or an incorrect routing rule.

Conclusion: freeze on control-plane failure

When StatsService or the management API becomes unavailable, the safest default is to preserve the last known route and raise an alert. Blindly switching without current evidence can turn a monitoring outage into a traffic outage.

Frequently asked questions

Can StatsService measure node latency by itself?

No. StatsService reports cumulative traffic statistics, such as uplink and downlink counters. Use observatory results or an external request that travels through the selected outbound to measure latency, then correlate that result with the counters.

Should I rewrite the complete Xray configuration after every failure?

Usually not. Keep several correctly configured outbounds with stable tags and select them through a balancer or a narrowly scoped runtime operation. Rewriting the entire file increases the chance of losing routing, DNS, transport, or API settings.

Why did a manual switch disappear after a subscription update?

Many clients regenerate the active configuration when a subscription is updated. Put the failover policy in the source configuration, use a stable balancer, or make the automation reapply a validated runtime change after the update.

What is a sensible first latency threshold?

Measure the normal p50 and p95 latency for at least several hours first. A practical starting point is a threshold around the normal p95 plus 30 to 50 percent, combined with three consecutive violations and a recovery requirement of five good samples.

Download v2rayN