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 connections
  • cMaxReuseTimes: Physical connection reuse limit before forced rotation
  • hMaxRequestTimes: Logical request limit per client
  • hMaxReusableSecs: Time‑based expiration for clients
  • hKeepAlivePeriod: 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:

  1. Creates a new client if under maxConnections limit and no suitable existing client exists
  2. Selects existing client based on OpenUsage (current streams) and LeftRequests remaining

Each XmuxClient wraps a raw connection implementing the XmuxConn interface. The client tracks:

  • OpenUsage: Active logical streams
  • LeftRequests: Remaining allowed requests before hard limit
  • UnreusableAt: 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

  1. 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
  2. Increment concurrency — Increase mux.concurrency from 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
  3. Optimize XMUX for Split‑HTTP — If using splithttp transport:

    • Set maxConcurrency range to 1.5–2× your expected parallel requests
    • Cap maxConnections to prevent socket exhaustion on mobile clients
    • Tune hKeepAlivePeriod to 50–75% of your NAT idle timeout
  4. Validate UDP handling — If your workload requires UDP/443 (QUIC, DNS‑over‑QUIC):

    • Set xudpConcurrency equal to concurrency
    • Change xudpProxyUDP443 to "allow"
    • Monitor for increased CPU from UDP‑over‑TCP encapsulation

Summary

  • Xray‑core implements two multiplexing layers: generic outbound Mux in common/mux/client.go and transport‑level XMUX in transport/internet/splithttp/mux.go
  • Outbound Mux uses ClientManager with an IncrementalWorkerPicker to distribute streams across ClientWorker instances, each respecting MaxConcurrency (default 8) and MaxConnection (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.concurrency for parallel streams, xmux.maxConnections for socket resource limits, and xmux.hKeepAlivePeriod for 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:

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 →