How to Implement Multi-Port Inbound with Different Protocols on Xray-Core
You can run multiple inbound handlers on a single Xray-core instance by defining separate inbound entries in your configuration, each with unique tags, distinct port ranges in receiver_settings, and protocol-specific proxy_settings blocks.
Xray-core supports running multiple proxy protocols simultaneously on different ports through its modular inbound handler architecture. Each listening endpoint consists of ReceiverSettings (defining where to listen) and ProxySettings (defining what protocol to run), managed independently by the inbound manager in app/proxyman/inbound/inbound.go. This allows a single Xray-core binary to handle VMess on port 443, VLESS on port 8443, and Shadowsocks on port 8388 simultaneously.
Understanding the Inbound Architecture
Xray-core treats every listening endpoint as a discrete inbound handler. Understanding the relationship between receiver configuration and proxy configuration is essential for implementing multi-port setups.
ReceiverConfig and Port Allocation
The ReceiverConfig (defined in app/proxyman/config.proto) specifies the network binding parameters:
- listen: The IP address to bind (e.g.,
"0.0.0.0"for all interfaces) - port_list: A
PortListmessage supporting single ports or ranges - stream_settings: TLS, XTLS, or transport layer configuration
According to the source code in app/proxyman/inbound/always.go, the AlwaysOnInboundHandler iterates over every port in the PortList and creates independent workers (implementations of tcpWorker, udpWorker, or dsWorker) for each.
ProxySettings and Protocol Selection
The ProxySettings (e.g., vmess.inbound.Config, vless.inbound.Config) define the protocol implementation and authentication:
- type: Protocol identifier (
"vmess","vless","trojan","shadowsocks", etc.) - users: Authentication credentials specific to the protocol
Each inbound handler combines one ReceiverConfig with one ProxySettings message. Because these are paired one-to-one, you must define separate inbound entries to run different protocols on different ports.
Configuration Examples
Xray-core supports JSON, YAML, TOML, and Protobuf configurations. Below are practical examples showing VMess on port 443 and VLESS on port 8443.
JSON Configuration
{
"inbound": [
{
"tag": "vmess-443",
"receiver_settings": {
"@type": "pipe",
"listen": "0.0.0.0",
"port_list": {
"range": [
{
"from": 443,
"to": 443
}
]
},
"sniffing_settings": {
"enabled": true,
"dest_override": ["http", "tls"]
}
},
"proxy_settings": {
"@type": "vmess",
"users": [
{
"id": "11111111-1111-1111-1111-111111111111",
"alterId": 64,
"level": 0,
"email": "user@example.com"
}
]
}
},
{
"tag": "vless-8443",
"receiver_settings": {
"@type": "pipe",
"listen": "0.0.0.0",
"port_list": {
"range": [
{
"from": 8443,
"to": 8443
}
]
}
},
"proxy_settings": {
"@type": "vless",
"users": [
{
"id": "22222222-2222-2222-2222-222222222222",
"encryption": "none",
"level": 0,
"email": "vless@example.com"
}
]
}
}
],
"outbound": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
}
]
}
YAML Configuration
inbound:
- tag: vmess-443
receiver_settings:
type: pipe
listen: 0.0.0.0
port_list:
range:
- from: 443
to: 443
sniffing_settings:
enabled: true
dest_override:
- http
- tls
proxy_settings:
type: vmess
users:
- id: 11111111-1111-1111-1111-111111111111
alterId: 64
level: 0
email: user@example.com
- tag: vless-8443
receiver_settings:
type: pipe
listen: 0.0.0.0
port_list:
range:
- from: 8443
to: 8443
proxy_settings:
type: vless
users:
- id: 22222222-2222-2222-2222-222222222222
encryption: none
level: 0
email: vless@example.com
outbound:
- tag: direct
protocol: freedom
settings: {}
Critical configuration notes:
- The
tagfield must be unique for each inbound handler; it serves as the key ininbound.Manager's handler map. - The
type: pipeinreceiver_settingsresolves toproxyman.ReceiverConfig. - A single
ReceiverConfigcannot host multiple protocols; each protocol requires its own inbound entry.
Internal Handler Wiring
Understanding how Xray-core processes your configuration helps debug multi-port setups.
Handler Creation Flow
When Xray starts, the following sequence occurs according to app/proxyman/inbound/inbound.go:
- Config parsing:
core.LoadConfigunmarshals the configuration intocore.Config(defined incore/config.pb.go), which contains a slice ofInboundHandlerConfigstructs. - Handler instantiation:
proxyman.NewHandler(ctx, cfg)(line 55) extracts:receiverSettingsas*proxyman.ReceiverConfigproxySettingsas*serial.TypedMessage
- Object creation: The function calls
common.CreateObjectto instantiate the protocol-specific inbound handler (e.g., VMess or VLESS). - Registration: The handler is registered in
inbound.Managerusing its tag.
Worker Generation and Port Binding
The NewAlwaysOnInboundHandler function in app/proxyman/inbound/always.go (lines 27-66) manages the actual network listeners:
- Iterates through the
PortListin theReceiverConfig - For each port, creates protocol-specific workers (
tcpWorker,udpWorker, ordsWorkerfor domain sockets) - Each worker calls
net.ListenTCPornet.ListenUDPindependently - All workers share the same
proxy.Inboundinstance but maintain separate socket references
When Manager.Start() executes (lines 8-25 of inbound.go), every registered handler starts its workers, resulting in independent listeners on the configured ports.
Practical Implementation Tips
-
Port ranges for single protocols: If you need a wide port range for the same protocol, use a single
ReceiverConfigwith multiple ranges inport_list. This is more efficient than creating separate inbound entries. -
Protocol-specific stream settings: When different protocols require different TLS certificates or transport settings (WebSocket, gRPC, etc.), define separate inbound entries each with their own
stream_settingsunderreceiver_settings. -
Debugging listener startup: Enable debug logging (
loglevel: debug) to verify worker creation. Theerrors.LogDebugcalls inalways.go(lines 31-33) output the specific address and port each worker binds to. -
Tag uniqueness: Duplicate tags cause registration failures in
inbound.Manager. Always use descriptive, unique tags like"vmess-tcp-443"or"trojan-ws-8443".
Summary
- Multi-port inbound requires separate inbound entries in your Xray-core configuration, each with unique tags.
- ReceiverSettings (
type: pipe) control where the server listens, supporting single ports or ranges viaport_list. - ProxySettings determine the protocol (VMess, VLESS, Trojan, etc.) and authentication for that specific listener.
- The inbound manager in
app/proxyman/inbound/inbound.goregisters each handler, whileNewAlwaysOnInboundHandlerinalways.gocreates independent workers for every specified port. - A single inbound entry cannot host multiple protocols; each protocol requires its own handler configuration.
Frequently Asked Questions
Can I run multiple protocols on the same port?
No. A single ReceiverConfig can only bind one protocol implementation. To handle multiple protocols on the same port (e.g., multiplexing TLS and non-TLS on 443), you must use a multiplexing transport like splithttp or place a reverse proxy (such as Nginx or HAProxy) in front of Xray-core to route traffic based on SNI or ALPN before it reaches the Xray listeners.
How do I configure a port range for one protocol?
Specify a range in the port_list field under receiver_settings. For example, setting from: 10000 and to: 10100 creates 101 separate workers listening on ports 10000 through 10100, all using the same protocol configuration. This is handled efficiently by the AlwaysOnInboundHandler without requiring 101 separate inbound entries.
Why does my multi-port configuration fail to start?
The most common causes are duplicate tag values (each inbound must have a unique tag) or port conflicts where another process is already bound to the specified port. Check the debug logs for errors from inbound.Manager or NewAlwaysOnInboundHandler regarding handler registration or net.Listen failures.
Can different inbound handlers share the same outbound routing?
Yes. The tag in inbound configuration is used for routing decisions, but multiple inbounds can route to the same outbound tag. Define your outbound handlers (e.g., "direct", "blocked", or a remote server) once, then reference the appropriate outbound tag in your routing rules based on the inbound tag or domain matching.
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 →