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
XrayBufferSizeenvironment 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:
-
Configuration parsing – Your JSON/YAML/TOML is unmarshaled into
app/policy.Config(generated fromconfig.proto) -
Default overlay –
app/policy/config.godefinesdefaultPolicy()which establishes baseline values for any unspecified fields -
Manager instantiation –
app/policy.Newcreates apolicy.Instancestoring level→Policy mappings -
Runtime lookup – Proxy handlers call
policyManager.ForLevel(user.Level)to obtain apolicy.Sessionwith resolved timeouts, stats flags, and buffer settings -
Context injection –
policy.ContextWithBufferPolicyembeds buffer settings into the request context for transport layers -
Transport consumption –
transport/pipe/pipe.goextracts 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 apolicy.Sessionfromfeatures/policy/policy.gopolicy.ContextWithBufferPolicy()stores buffer settings in the request context- The pipe transport (
transport/pipe/pipe.go) retrieves these settings viapolicy.BufferPolicyFromContext()
Summary
-
Xray-core policy settings are configured through the
policysection 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.connectionor theXrayBufferSizeenvironment variable, with-1or0enabling unlimited mode. -
Implementation files include
app/policy/config.proto(schema),app/policy/config.go(defaults),app/policy/manager.go(lookup), andfeatures/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →