How Xray-core's Balancer Override Feature Enables Dynamic Load Balancing Changes

Xray-core's balancer override feature lets operators change load balancing targets at runtime via API calls without restarting the proxy or editing configuration files.

The balancer override mechanism in XTLS/Xray-core bridges the gap between static configuration and operational flexibility. Proxy administrators often need to reroute traffic during incidents, maintenance windows, or performance optimizations—this feature solves that by injecting runtime overrides directly into the routing layer. This article breaks down the implementation from API surface to core routing logic, referencing actual source paths and method signatures from the Xray-core repository.


API Entry Point: The api bo Command

The override feature exposes a clean CLI interface through main/commands/all/api/balancer_override.go. This command constructs and dispatches gRPC requests to the internal routing service.

Command Structure


# Force a specific balancer to use a designated outbound

xray api bo -b <balancer_tag> <outbound_tag>

# Remove the override and restore normal load balancing

xray api bo -b <balancer_tag> -r

The -b flag specifies the balancer tag to target, while the positional argument or -r flag determines whether to set or clear the override.

gRPC Client Construction

Inside balancer_override.go, the command instantiates a RoutingService client and builds an OverrideBalancerTargetRequest:

request := &command.OverrideBalancerTargetRequest{
    BalancerTag: *balancerTag,
    Target:      args.First(),
}

The Target field carries the forced outbound tag—or remains empty when removing an override.


RoutingService RPC Implementation

The server-side handling lives in app/router/command/command.go. The OverrideBalancerTarget method receives requests and delegates to the underlying router through the routing.BalancerOverrider interface.

Type Assertion and Delegation

func (s *RoutingService) OverrideBalancerTarget(ctx context.Context, request *OverrideBalancerTargetRequest) (*OverrideBalancerTargetResponse, error) {
    if bo, ok := s.router.(routing.BalancerOverrider); ok {
        return &OverrideBalancerTargetResponse{}, bo.SetOverrideTarget(request.BalancerTag, request.Target)
    }
    return nil, errors.New("router does not implement BalancerOverrider")
}

This design maintains clean separation: the command layer handles transport, while the router layer manages state.


Core Router: Override Storage and Retrieval

The app/router/balancing.go file implements the actual override logic. The Router struct maintains a map of balancers[tag]*Balancer, and each Balancer carries a private override field.

SetOverrideTarget Implementation

func (r *Router) SetOverrideTarget(tag, target string) error {
    if b, ok := r.balancers[tag]; ok {
        b.override.Put(target)  // thread-safe write
        return nil
    }
    return errors.New("cannot find tag: " + tag)
}

The Put method synchronizes access to prevent race conditions during concurrent API calls and routing decisions.

GetOverrideTarget Implementation

func (r *Router) GetOverrideTarget(tag string) (string, bool) {
    if b, ok := r.balancers[tag]; ok {
        return b.override.Get(), true
    }
    return "", false
}

This method supports the api bi (balancer-info) command for operational visibility.


Thread-Safe Override Data Structure

The app/router/balancing_override.go file defines the override struct, which wraps a string with sync.RWMutex protection.

Implementation Details

type override struct {
    target string
    mu     sync.RWMutex
}

func (o *override) Get() string {
    o.mu.RLock()
    defer o.mu.RUnlock()
    return o.target
}

func (o *override) Put(target string) {
    o.mu.Lock()
    defer o.mu.Unlock()
    o.target = target
}

func (o *override) Clear() {
    o.Put("")
}

Read-heavy operations (routing decisions) use RLock for concurrency, while write operations (API updates) acquire exclusive locks.


Balancing Decision Point: PickOutbound

The critical integration occurs in app/router/balancing.go within the PickOutbound method. Every routing decision checks the override before consulting the load-balancing strategy.

Override Short-Circuit Logic

func (b *Balancer) PickOutbound(candidates []string) (string, error) {
    // Check for active override first
    if o := b.override.Get(); o != "" {
        return o, nil  //forced selection, bypass strategy
    }
    
    // No override: delegate to configured strategy
    return b.strategy.PickOutbound(candidates)
}

When an override exists, the balancer returns it immediately. When empty or cleared, normal strategy behavior resumes—whether round-robin, least-latency, random, or custom implementations.


Operational Visibility: The api bi Command

The main/commands/all/api/balancer_info.go command complements the override feature by exposing current state. It calls GetOverrideTarget and displays both the active override and the strategy's normal selection.

Typical Output


Balancer Tag: myBalancer
Strategy: roundRobin
Override Target: fast-proxy (active)
Principle Target: server-03

This visibility prevents configuration drift and aids incident response.


Practical Code Examples

CLI Override Operations


# Emergency traffic diversion: force all traffic through emergency proxy

xray api bo -b production-balancer emergency-proxy

# Maintenance complete: restore automatic load balancing

xray api bo -b production-balancer -r

# Verify current state

xray api bi -b production-balancer

Programmatic Override in Go

package main

import (
    "context"
    "log"
    "time"

    "github.com/xtls/xray-core/app/router/command"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    client := command.NewRoutingServiceClient(conn)

    // Apply override: force balancer to use "direct" outbound
    _, err = client.OverrideBalancerTarget(ctx, &command.OverrideBalancerTargetRequest{
        BalancerTag: "api_balancer",
        Target:      "direct",
    })
    if err != nil {
        log.Fatal("override failed:", err)
    }
    log.Println("Override applied successfully")

    // Clear override to restore normal behavior
    _, err = client.OverrideBalancerTarget(ctx, &command.OverrideBalancerTargetRequest{
        BalancerTag: "api_balancer",
        Target:      "", // empty string clears override
    })
    if err != nil {
        log.Fatal("clear failed:", err)
    }
    log.Println("Override cleared, normal balancing restored")
}

Key Implementation Files

File Purpose
main/commands/all/api/balancer_override.go CLI command parsing and gRPC client construction
app/router/command/command.go RoutingService RPC handler with OverrideBalancerTarget
app/router/balancing.go Core router with SetOverrideTarget, GetOverrideTarget, and PickOutbound override logic
app/router/balancing_override.go Thread-safe override struct with sync.RWMutex
main/commands/all/api/balancer_info.go Status query command for operational visibility

Summary

  • Xray-core's balancer override enables runtime traffic redirection without process restarts or configuration reloads
  • The feature flows through CLI → gRPC → RoutingService → Router → Balancer with clean interface boundaries
  • Thread-safe storage via sync.RWMutex protects concurrent reads (routing decisions) and writes (API updates)
  • Override short-circuits the normal PickOutbound strategy, forcing a specific outbound tag when active
  • Empty target clears the override, immediately restoring the configured load-balancing strategy

Frequently Asked Questions

How do I check if a balancer override is currently active?

Use the xray api bi -b <balancer_tag> command or call the GetBalancerInfo RPC. The response includes both the override field (active forced target) and the principleTarget (what the strategy would normally select).

Does setting an override persist across Xray-core restarts?

No. The balancer override is purely in-memory. When the process restarts, all overrides are cleared and balancing resumes according to the static configuration file. For persistent changes, modify the config and restart.

Can multiple balancers have independent overrides simultaneously?

Yes. Each balancer maintains its own override struct. The balancers map in app/router/balancing.go stores per-balancer state, so you can force balancer-A to outbound-1 while balancer-B continues normal operation or points to outbound-2.

What happens to existing connections when an override is applied?

Existing connections are not affected. The override only influences new routing decisions made after the SetOverrideTarget call completes. Established TCP streams, UDP sessions, and active WebSocket connections continue through their originally selected outbound.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →