How Xray‑core Handles Connection Multiplexing and Performance Tuning
Xray‑core uses two layers of connection multiplexing: an outbound Mux layer that reuses TCP connections for multiple logical streams, and a transport‑level XMUX layer for HTTP‑based protocols, both configurable through concurrency limits, connection pools, and keep‑alive settings.
Connection multiplexing in Xray‑core reduces TCP handshake overhead and improves throughput on high‑latency networks. The implementation spans the generic outbound layer in app/proxyman/outbound/handler.go and the transport‑specific Split‑HTTP multiplexer in transport/internet/splithttp/mux.go. This guide explains both architectures and provides concrete tuning parameters based on the source code.
Outbound Mux: Generic TCP Connection Reuse
Configuration and Initialization
The outbound Mux is controlled through the mux object in your outbound configuration. The struct definition resides in infra/conf/xray.go at lines 101–122, where MuxConfig.Build() produces a proxyman.MultiplexingConfig protobuf message.
{
"protocol": "vmess",
"settings": { },
"mux": {
"enabled": true,
"concurrency": 16,
"xudpConcurrency": 0,
"xudpProxyUDP443": "reject"
}
}
When NewHandler constructs the outbound handler in app/proxyman/outbound/handler.go (lines 22–45), it checks senderSettings.MultiplexSettings.Enabled. If true, it instantiates a mux.ClientManager with the provided limits.
ClientManager and Worker Selection
The ClientManager in common/mux/client.go (lines 25–41) maintains an IncrementalWorkerPicker that distributes incoming streams across a pool of ClientWorker instances.
// Abstract structure from common/mux/client.go
type ClientManager struct {
picker *IncrementalWorkerPicker
config *MultiplexingConfig
}
func (m *ClientManager) Dispatch(ctx context.Context, link *transport.Link) error {
// Picker selects or creates a ClientWorker
worker := m.picker.Pick()
return worker.Dispatch(ctx, link)
}
The IncrementalWorkerPicker (lines 48–86) implements an incrementing strategy: it tries to fill existing workers before spawning new ones. Each worker tracks:
- MaxConcurrency: Maximum simultaneous streams per worker (default 8, mapped from
mux.concurrency) - MaxConnection: Maximum total workers (TCP connections) allowed (hard‑coded to 128 in the outbound handler)
When a worker reaches its concurrency limit, the picker creates a new ClientWorker. Idle workers are cleaned up after 30 seconds.
Session Management and Data Flow
Each ClientWorker contains a SessionManager that maps session IDs to active streams. A background goroutine reads from the shared TCP connection in client.go and demuxes frames to the correct session based on the session ID header.
The frame format uses a small header (session ID + length) followed by payload data. This allows multiple logical streams to interleave on a single TCP connection without head‑of‑line blocking at the application layer, though TCP‑level head‑of‑line blocking still applies.
Transport‑Level XMUX: Split‑HTTP Multiplexing
Purpose and Architecture
XMUX operates within the Split‑HTTP transport for protocols like VLESS and VMess over HTTP. It provides finer‑grained control over connection pooling than the generic outbound Mux. The implementation lives in transport/internet/splithttp/mux.go (lines 44–108).
Unlike the outbound Mux which creates workers dynamically, XmuxManager maintains a pool of XmuxClient objects with explicit lifecycle controls:
maxConcurrency: Streams per client (randomized within configured range)maxConnections: Hard cap on total TCP connectionscMaxReuseTimes: Physical connection reuse limit before forced rotationhMaxRequestTimes: Logical request limit per clienthMaxReusableSecs: Time‑based expiration for clientshKeepAlivePeriod: Keep‑alive probe interval
Configuration Structure
The XmuxConfig struct in transport/internet/splithttp/config.go (lines 420–464) uses RangeConfig types that accept single values or [min, max] arrays for randomized selection:
{
"protocol": "splithttp",
"settings": {
"servers": [{
"address": "example.com",
"port": 443,
"xmux": {
"maxConcurrency": { "value": [12, 24] },
"maxConnections": { "value": [2] },
"cMaxReuseTimes": { "value": [200] },
"hMaxRequestTimes": { "value": [200] },
"hMaxReusableSecs": { "value": [300] },
"hKeepAlivePeriod": 30
}
}]
}
}
The GetNormalized*() methods in config.go clamp values to sensible bounds and handle the range expansion.
Client Lifecycle Management
When GetXmuxClient is called in mux.go, the manager performs cleanup of closed or exhausted clients, then either:
- Creates a new client if under
maxConnectionslimit and no suitable existing client exists - Selects existing client based on
OpenUsage(current streams) andLeftRequestsremaining
Each XmuxClient wraps a raw connection implementing the XmuxConn interface. The client tracks:
OpenUsage: Active logical streamsLeftRequests: Remaining allowed requests before hard limitUnreusableAt: Optional timestamp for time‑based expiration
This design allows aggressive connection reuse while preventing starvation of new streams and enabling proactive rotation of connections that may have degraded quality (NAT rebinding, middlebox state timeouts).
Performance Tuning Guide
Outbound Mux Parameters
| Parameter | Default | Recommended Range | Effect |
|---|---|---|---|
mux.concurrency |
8 | 16–128 | Higher values reduce connection count but increase head‑of‑line blocking risk |
mux.xudpConcurrency |
0 (disabled) | 0 or match concurrency |
UDP-over-TCP multiplexing; enable only when needed |
mux.xudpProxyUDP443 |
"reject" |
"reject" or "allow" |
Security vs. compatibility trade-off |
XMUX Transport Parameters
| Parameter | Typical Value | Tuning Guidance |
|---|---|---|
maxConcurrency |
[8, 16] |
Adjust based on target server capacity; higher values for low-latency paths |
maxConnections |
[2, 4] on mobile, [8, 16] on servers |
Balance between socket resource usage and throughput |
cMaxReuseTimes |
100–500 |
Lower for networks with frequent NAT rebinding; higher for stable DC paths |
hMaxReusableSecs |
180–600 |
Shorter intervals proactively recycle connections before middlebox timeouts |
hKeepAlivePeriod |
30–60 |
Match to your NAT/middlebox idle timeout (often 60–120s) |
Practical Tuning Workflow
-
Establish baseline metrics — Run sustained traffic through your Xray‑core instance and capture:
- TCP connection count (
ss -tan | grep ESTABLISHED | wc -l) - Per-stream latency (application‑level RTT)
- Throughput saturation point
- TCP connection count (
-
Increment concurrency — Increase
mux.concurrencyfrom 8 to 16, 32, 64, testing each step. Stop when:- Latency variance increases (>25% jitter)
- Throughput plateaus
- Log shows "worker full" messages requiring new connections
-
Optimize XMUX for Split‑HTTP — If using
splithttptransport:- Set
maxConcurrencyrange to 1.5–2× your expected parallel requests - Cap
maxConnectionsto prevent socket exhaustion on mobile clients - Tune
hKeepAlivePeriodto 50–75% of your NAT idle timeout
- Set
-
Validate UDP handling — If your workload requires UDP/443 (QUIC, DNS‑over‑QUIC):
- Set
xudpConcurrencyequal toconcurrency - Change
xudpProxyUDP443to"allow" - Monitor for increased CPU from UDP‑over‑TCP encapsulation
- Set
Summary
- Xray‑core implements two multiplexing layers: generic outbound Mux in
common/mux/client.goand transport‑level XMUX intransport/internet/splithttp/mux.go - Outbound Mux uses
ClientManagerwith anIncrementalWorkerPickerto distribute streams acrossClientWorkerinstances, each respectingMaxConcurrency(default 8) andMaxConnection(hard‑coded 128) - XMUX provides finer‑grained control over connection lifecycle with randomized ranges for concurrency, connection caps, reuse limits, and keep‑alive periods
- Key tuning parameters:
mux.concurrencyfor parallel streams,xmux.maxConnectionsfor socket resource limits, andxmux.hKeepAlivePeriodfor NAT/middlebox compatibility - Performance validation requires incremental testing with real traffic to identify the saturation point where increased concurrency no longer improves throughput
Frequently Asked Questions
What is the difference between mux.concurrency and xmux.maxConcurrency?
mux.concurrency controls the generic outbound multiplexer in common/mux/client.go, limiting simultaneous streams per TCP connection for protocols like VMess and VLESS over raw TCP. xmux.maxConcurrency is specific to the Split‑HTTP transport (transport/internet/splithttp/mux.go) and governs how many HTTP streams share a single underlying connection. Both parameters achieve similar goals but operate at different layers and apply to different transport combinations.
Why does Xray‑core create multiple TCP connections despite mux being enabled?
The outbound Mux creates new ClientWorker instances (new TCP connections) when existing workers reach their MaxConcurrency limit or when the hard‑coded MaxConnection pool of 128 is not yet exhausted. This behavior is intentional: it prevents head‑of‑line blocking from saturating all traffic. If you observe excessive connections, increase mux.concurrency to allow more streams per worker, or verify that your workload actually benefits from multiplexing rather than parallel connections.
How do I tune XMUX for mobile devices with limited sockets?
Mobile operating systems often restrict the number of open file descriptors per app. For Split‑HTTP transports, set xmux.maxConnections to a low fixed value like 2 or 4 to cap total TCP sockets. Increase xmux.maxConcurrency to 16 or 32 to compensate by packing more streams per connection. Reduce xmux.hKeepAlivePeriod to 30 seconds to prevent NAT timeouts without maintaining excessive idle connections. Monitor with lsof or equivalent to confirm your process stays within platform limits.
When should I enable xudpConcurrency for UDP traffic?
Enable xudpConcurrency when your application requires tunneling protocols that use UDP/443, such as QUIC, DNS‑over‑QUIC, or HTTP/3. The default value of 0 disables UDP‑over‑TCP multiplexing, causing UDP traffic to bypass the Mux layer entirely. Set xudpConcurrency equal to your mux.concurrency value to ensure UDP streams receive the same parallelism as TCP streams. Always pair this with xudpProxyUDP443: "allow" in your configuration, and monitor CPU usage since UDP‑over‑TCP encapsulation adds processing overhead.
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 →