How WireGuard Inbound Works in Xray-core: Complete Implementation Guide
The Xray-core WireGuard inbound creates a virtual TUN interface, decrypts UDP packets from WireGuard peers, and forwards clear-text traffic into Xray's standard routing pipeline.
Xray-core's WireGuard inbound implementation allows you to receive encrypted WireGuard traffic and process it through Xray's powerful routing, DNS, and policy engine. This article explains how the WireGuard inbound works internally and provides complete configuration guidance based on the actual source code in XTLS/Xray-core.
Architecture Overview
The WireGuard inbound in Xray-core operates through a layered architecture that bridges WireGuard's encryption with Xray's proxy pipeline.
Core Components
| Component | Role | Source Location |
|---|---|---|
DeviceConfig |
Holds all WireGuard settings including peers, endpoints, MTU, and domain strategy | proxy/wireguard/config.proto |
NewServer |
Entry point that parses endpoints, selects TUN implementation, and builds the WireGuard device | proxy/wireguard/server.go |
Server.Process |
Handles incoming UDP packets and pushes them to the read queue | proxy/wireguard/server.go |
forwardConnection |
Creates Xray sub-contexts and dispatches decrypted traffic to the routing pipeline | proxy/wireguard/server.go |
| TUN creator | Decides between kernel-mode TUN (createKernelTun) or userspace gVisor TUN (createGVisorTun) |
proxy/wireguard/config.go |
netBindServer |
Implements the bind side of the WireGuard device, handling packet rewriting and queue management | proxy/wireguard/server.go |
Traffic Flow Through the System
-
Initialization: Xray reads the inbound configuration and calls
wireguard.NewServerinproxy/wireguard/server.go -
Endpoint parsing: The
parseEndpointsfunction extracts IPv4/IPv6 endpoints and determines address families for DNS lookups -
TUN creation:
conf.createTun()returns atunCreator. The system checksIsClient,NoKernelTun, andKernelTunSupported()to choose between kernel TUN or gVisor-based TUN -
Device building:
tun.BuildDevice(createIPCRequest(conf), server.bindServer)creates the WireGuard device and attaches thenetBindServer -
Packet reception: When UDP datagrams arrive,
Server.Processparses the remote endpoint, wraps the connection into abuf.PacketReader, and pushes packets ontobindServer.readQueue -
Connection forwarding: For each outgoing clear-text connection,
forwardConnectionbuilds a sub-context with inbound name "wireguard", setsinbound.Sourceto the peer's address, and callsdispatcher.DispatchLinkto hand control to Xray's routing, DNS, and policy engine
Configuration Reference
The WireGuard inbound configuration is defined in proxy/wireguard/config.proto at lines 17-34. Understanding these fields is essential for proper setup.
Configuration Schema
| Field | Type | Description |
|---|---|---|
secret_key |
string | Server's private key (keep secure) |
peers |
repeated PeerConfig |
List of allowed peers |
endpoint |
repeated string | Local listening addresses |
mtu |
int32 | Interface MTU (default 1420) |
num_workers |
int32 | Worker goroutines for bind server |
domain_strategy |
enum | IP version handling strategy |
is_client |
bool | Client mode flag (false for inbound) |
no_kernel_tun |
bool | Force gVisor TUN instead of kernel |
Peer Configuration
Each peer in the peers array uses PeerConfig from proxy/wireguard/config.proto:
| Field | Description |
|---|---|
public_key |
Peer's public key |
pre_shared_key |
Optional pre-shared key for additional security |
endpoint |
Peer's external endpoint (IP:port) |
allowed_ips |
CIDR ranges this peer can route |
keep_alive |
Persistent keepalive interval in seconds |
Complete Configuration Examples
Minimal Server Configuration (YAML)
This configuration sets up a basic WireGuard inbound in Xray-core:
inbounds:
- tag: wg-in
type: wireguard
listen: 0.0.0.0:51820
domain_strategy: FORCE_IP46
mtu: 1420
num_workers: 2
is_client: false
no_kernel_tun: false
peers:
- public_key: "YOUR_PEER_PUBLIC_KEY_BASE64"
pre_shared_key: ""
endpoint: "203.0.113.5:51820"
allowed_ips:
- "0.0.0.0/0"
- "::/0"
keep_alive: 25
secret_key: "YOUR_SERVER_PRIVATE_KEY_BASE64"
Key configuration points:
type: wireguardactivates the inbound implementation inproxy/wireguard/server.gois_client: falseputs the inbound in server mode (required for inbound operation)no_kernel_tun: falseallows automatic selection between kernel and gVisor TUN
Multiple Peers Configuration
inbounds:
- tag: wg-in-multi
type: wireguard
listen: 0.0.0.0:51820
domain_strategy: FORCE_IP46
mtu: 1420
num_workers: 4
is_client: false
no_kernel_tun: false
peers:
- public_key: "PEER1_PUBLIC_KEY"
allowed_ips:
- "10.0.0.2/32"
keep_alive: 25
- public_key: "PEER2_PUBLIC_KEY"
allowed_ips:
- "10.0.0.3/32"
keep_alive: 25
- public_key: "PEER3_PUBLIC_KEY"
allowed_ips:
- "10.0.0.0/24"
keep_alive: 60
secret_key: "SERVER_PRIVATE_KEY"
Programmatic Configuration (Go)
For direct integration with Xray-core's Go API:
package main
import (
"context"
"github.com/xtls/xray-core/proxy/wireguard"
)
func createWireGuardInbound() (*wireguard.Server, error) {
conf := &wireguard.DeviceConfig{
SecretKey: "SERVER_PRIVATE_KEY_BASE64",
Endpoint: []string{"0.0.0.0:51820"},
Peers: []*wireguard.PeerConfig{
{
PublicKey: "PEER_PUBLIC_KEY_BASE64",
PreSharedKey: "",
Endpoint: "203.0.113.5:51820",
AllowedIps: []string{"0.0.0.0/0", "::/0"},
KeepAlive: 25,
},
},
Mtu: 1420,
NumWorkers: 2,
DomainStrategy: wireguard.DeviceConfig_FORCE_IP46,
IsClient: false,
NoKernelTun: false,
}
srv, err := wireguard.NewServer(context.Background(), conf)
if err != nil {
return nil, err
}
return srv, nil
}
The NewServer function in proxy/wireguard/server.go performs all initialization steps: endpoint parsing, TUN selection, and device building.
TUN Implementation Selection
The WireGuard inbound in Xray-core supports two TUN implementations, selected automatically or forced via configuration.
Selection Logic
The decision happens in proxy/wireguard/config.go lines 33-53:
func (c *DeviceConfig) createTun() tunCreator {
// Check if we should use kernel TUN
if !c.IsClient && !c.NoKernelTun && KernelTunSupported() {
return createKernelTun
}
// Fall back to gVisor userspace netstack
return createGVisorTun
}
Kernel TUN (createKernelTun)
- Uses the operating system's native TUN interface
- Better performance, lower latency
- Requires appropriate permissions (usually root or
CAP_NET_ADMIN) - Platform-dependent: Linux supports it fully; other platforms may vary
gVisor TUN (createGVisorTun)
- Pure userspace implementation using Google's gVisor netstack
- No special permissions required
- More portable across platforms
- Slightly higher overhead than kernel TUN
Configuration Control
| Setting | Effect |
|---|---|
no_kernel_tun: true |
Forces gVisor TUN regardless of kernel support |
no_kernel_tun: false |
Allows automatic selection (default) |
is_client: true |
In client mode, typically uses gVisor TUN |
Key Source Files Reference
Understanding the WireGuard inbound implementation requires familiarity with these files in the Xray-core repository:
| File | Purpose | Lines of Interest |
|---|---|---|
proxy/wireguard/config.proto |
Protobuf schema for DeviceConfig and PeerConfig |
17-34 |
proxy/wireguard/server.go |
Core inbound implementation: NewServer, Process, forwardConnection |
36-70, 77-124, 26-71 |
proxy/wireguard/config.go |
TUN selection logic and configuration helpers | 33-53 |
proxy/wireguard/tun_linux.go |
Linux-specific kernel TUN implementation | Full file |
proxy/wireguard/tun_default.go |
Fallback TUN implementations for other platforms | Full file |
testing/scenarios/wireguard_test.go |
End-to-end integration tests | Full file |
Summary
-
WireGuard inbound in Xray-core creates a virtual TUN interface that receives encrypted UDP packets, decrypts them, and forwards clear-text traffic into Xray's standard routing pipeline.
-
Core implementation resides in
proxy/wireguard/server.go, withNewServerhandling initialization andServer.Processmanaging packet reception. -
TUN selection automatically chooses between kernel-mode TUN (better performance) and gVisor userspace TUN (better portability) based on
IsClient,NoKernelTun, andKernelTunSupported()inproxy/wireguard/config.go. -
Configuration uses
DeviceConfigandPeerConfigdefined inproxy/wireguard/config.proto, supporting YAML, JSON, and programmatic Go configuration. -
Traffic forwarding uses
forwardConnectioninproxy/wireguard/server.goto create properly tagged sub-contexts that integrate with Xray'srouting.Dispatcher.
Frequently Asked Questions
What is the difference between kernel TUN and gVisor TUN in Xray-core's WireGuard inbound?
Kernel TUN uses the operating system's native virtual network interface, providing better performance and lower latency but requiring appropriate permissions (typically root or CAP_NET_ADMIN). GVisor TUN is a pure userspace implementation using Google's gVisor netstack, offering broader platform compatibility without special permissions but with slightly higher overhead. The automatic selection logic in proxy/wireguard/config.go prefers kernel TUN for server mode on Linux when available.
How do I force Xray-core to use gVisor TUN instead of kernel TUN?
Set no_kernel_tun: true in your WireGuard inbound configuration. This forces the createTun() function in proxy/wireguard/config.go to return createGVisorTun regardless of whether kernel TUN support is available. This is useful when running without root privileges or on platforms where kernel TUN is unreliable.
Can I use the same WireGuard configuration for both client and server in Xray-core?
No, the is_client flag fundamentally changes the TUN implementation selection and traffic direction. For inbound operation (receiving WireGuard connections), you must set is_client: false. The server mode enables the full packet processing pipeline in Server.Process and forwardConnection. Client mode uses a different code path optimized for outbound connections.
What happens to decrypted traffic after it leaves the WireGuard TUN?
Decrypted traffic passes through forwardConnection in proxy/wireguard/server.go, which creates a new Xray sub-context with inbound tag "wireguard", sets the source address to the original peer's address, and calls dispatcher.DispatchLink. This integrates the traffic into Xray's normal routing pipeline, where it can be processed by rules, DNS, outbound handlers, and other Xray features just like any other inbound traffic.
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 →