How to Configure Xray-core Policy Settings for Timeouts, Stats, and Buffer Sizes

You configure Xray-core policy settings through the policy section of your JSON, YAML, or TOML configuration file, defining per-level timeouts, statistics collection, and buffer allocations that override the internal defaults defined in features/policy/policy.go.

The Xray-core proxy platform uses a policy manager to enforce connection handling rules, traffic accounting, and memory management. These policies are defined in the app/policy package and loaded at startup from your main configuration file. Understanding how to tune these settings allows you to optimize performance for high-throughput scenarios, reduce memory footprint on resource-constrained systems, and enable detailed traffic monitoring.

Policy Configuration Structure

The policy configuration consists of two main branches: level-specific policies (policy.level) that apply to individual users by their level ID, and system policies (policy.system) that apply globally across all inbound and outbound handlers.

Timeout Settings

The policy.timeout section controls how long Xray-core waits during various connection phases. These values are defined as seconds in the configuration, then converted to Go time.Duration values internally via the Second.Duration() helper in app/policy/config.go.

Timeout Field Purpose Default Value
handshake Time to complete TLS/WS handshake 60 seconds
connection_idle Time to keep idle connection alive 300 seconds
uplink_only Grace period after client closes write 1 second
downlink_only Grace period after server closes write 1 second

These defaults are hardcoded in features/policy/policy.go and can be overridden per user level.

Stats Settings

The policy.stats section enables per-user traffic accounting and online tracking. When enabled, Xray-core maintains counters in memory that can be queried via the Stats API or logged for analysis.

Stat Field When Enabled
user_uplink Tracks bytes sent by the user (client → proxy)
user_downlink Tracks bytes received by the user (proxy → client)
user_online Maintains a map of currently active connections per user

System-level stats are configured separately under policy.system.stats and apply to all inbound/outbound handlers regardless of user level.

Buffer Settings

The policy.buffer section controls per-connection memory allocation. This is particularly important for high-throughput proxies where default buffer sizing may create bottlenecks or excessive memory pressure.

Buffer Field Description
connection Bytes to allocate per connection. -1 means unlimited.

The default buffer size is determined at runtime in features/policy/policy.go based on:

  • The XrayBufferSize environment variable (value in MiB)
  • CPU architecture detection (some architectures receive larger default buffers)

When XrayBufferSize=0, the pipe transport uses unlimited buffering (-1), which can improve throughput at the cost of unbounded memory growth during backpressure.

Loading Policy Configuration

The policy manager follows a specific lifecycle from configuration to runtime enforcement:

  1. Configuration parsing – Your JSON/YAML/TOML is unmarshaled into app/policy.Config (generated from config.proto)

  2. Default overlay – app/policy/config.go defines defaultPolicy() which establishes baseline values for any unspecified fields

  3. Manager instantiation – app/policy.New creates a policy.Instance storing level→Policy mappings

  4. Runtime lookup – Proxy handlers call policyManager.ForLevel(user.Level) to obtain a policy.Session with resolved timeouts, stats flags, and buffer settings

  5. Context injection – policy.ContextWithBufferPolicy embeds buffer settings into the request context for transport layers

  6. Transport consumption – transport/pipe/pipe.go extracts and applies the buffer policy when creating data pipes

Practical Configuration Examples

Minimal JSON Configuration

This example configures a level-1 user with tightened timeouts, full stats collection, and a 1KB buffer limit:

{
  "policy": {
    "level": {
      "1": {
        "timeout": {
          "handshake": { "value": 30 },
          "connection_idle": { "value": 120 },
          "uplink_only": { "value": 5 },
          "downlink_only": { "value": 5 }
        },
        "stats": {
          "user_uplink": true,
          "user_downlink": true,
          "user_online": true
        },
        "buffer": {
          "connection": 1024
        }
      }
    },
    "system": {
      "stats": {
        "inbound_uplink": true,
        "inbound_downlink": true,
        "outbound_uplink": true,
        "outbound_downlink": true
      }
    }
  }
}

Equivalent YAML Configuration

YAML offers cleaner syntax for the same policy structure:

policy:
  level:
    "1":
      timeout:
        handshake: { value: 30 }
        connection_idle: { value: 120 }
        uplink_only: { value: 5 }
        downlink_only: { value: 5 }
      stats:
        user_uplink: true
        user_downlink: true
        user_online: true
      buffer:
        connection: 1024  # 1 KB

  system:
    stats:
      inbound_uplink: true
      inbound_downlink: true
      outbound_uplink: true
      outbound_downlink: true

Environment Variable Buffer Override

For system-wide buffer tuning without modifying configuration files:


# Set 2 MiB buffers for all connections

export XrayBufferSize=2
./xray -c config.json

# Disable buffer limits entirely (unlimited mode)

export XrayBufferSize=0
./xray -c config.json

The XrayBufferSize value is parsed in features/policy/policy.go and converted to bytes for the per-connection buffer allocation.

Code-Level Policy Integration

Proxy handlers integrate with the policy manager through the standard interface defined in app/policy/manager.go. Here's how the VLESS inbound handler (excerpted from proxy/vless/inbound/inbound.go) obtains and applies policy settings:

func (h *Handler) handleConnection(ctx context.Context, request *vless.Request) error {
    // Acquire policy manager from core instance
    pm := h.policyManager
    
    // Resolve session policy for this user's level
    sess := pm.ForLevel(request.User.Level)
    
    // Inject buffer policy into context for transport layer
    ctx = policy.ContextWithBufferPolicy(ctx, sess.Buffer)
    
    // Apply handshake timeout from policy
    conn.SetDeadline(time.Now().Add(sess.Timeouts.Handshake))
    
    // Check if user stats should be recorded
    if sess.Stats.UserUplink {
        // Record uplink traffic...
    }
    
    // Continue with connection handling...
}

Key integration points:

  • pm.ForLevel() returns a policy.Session from features/policy/policy.go
  • policy.ContextWithBufferPolicy() stores buffer settings in the request context
  • The pipe transport (transport/pipe/pipe.go) retrieves these settings via policy.BufferPolicyFromContext()

Summary

  • Xray-core policy settings are configured through the policy section of your main configuration file, supporting JSON, YAML, and TOML formats.

  • Timeout policies control handshake (default 60s), idle connection (default 300s), and uplink/downlink-only periods (default 1s), defined in features/policy/policy.go.

  • Stats policies enable per-user traffic counters (user_uplink, user_downlink) and online tracking (user_online), plus system-wide inbound/outbound statistics.

  • Buffer policies set per-connection memory allocation via policy.buffer.connection or the XrayBufferSize environment variable, with -1 or 0 enabling unlimited mode.

  • Implementation files include app/policy/config.proto (schema), app/policy/config.go (defaults), app/policy/manager.go (lookup), and features/policy/policy.go (runtime structures).

Frequently Asked Questions

How do I enable unlimited buffer sizes in Xray-core?

Set the XrayBufferSize environment variable to 0 before starting Xray-core, or configure policy.buffer.connection: -1 for a specific user level. According to features/policy/policy.go, both values trigger unlimited buffer allocation in the pipe transport (transport/pipe/pipe.go), which removes backpressure limits but may cause unbounded memory growth during traffic spikes.

What's the difference between user-level and system-level stats?

User-level stats (policy.level.*.stats) track per-user traffic and connection state, requiring each user to have a defined level ID in your authentication configuration. System-level stats (policy.system.stats) apply globally to all inbound and outbound handlers regardless of user identity, as defined in app/policy/config.proto. Enable both when you need granular per-user accounting plus aggregate infrastructure monitoring.

Why are my timeout changes not taking effect?

Verify that your user accounts reference the correct level ID matching your policy.level configuration. The policy manager (app/policy/manager.go) resolves settings via ForLevel(user.Level), so a mismatch between the user's assigned level and your policy keys causes fallback to defaults from defaultPolicy() in app/policy/config.go. Also confirm that timeout values are specified as objects with a value field (e.g., {"value": 30}), not bare integers.

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 →