# How WireGuard Inbound Works in Xray-core: Complete Implementation Guide

> Learn how WireGuard inbound works in Xray-core. This guide explains creating TUN interfaces, decrypting UDP, and routing traffic for a complete implementation.

- Repository: [Project X Community, Not Porn-jet X Hub/Xray-core](https://github.com/XTLS/Xray-core)
- Tags: how-to-guide
- Published: 2026-04-21

---

**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`](https://github.com/XTLS/Xray-core/blob/main/proxy/wireguard/server.go) |
| `Server.Process` | Handles incoming UDP packets and pushes them to the read queue | [`proxy/wireguard/server.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/wireguard/server.go) |
| `forwardConnection` | Creates Xray sub-contexts and dispatches decrypted traffic to the routing pipeline | [`proxy/wireguard/server.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/wireguard/server.go) |
| TUN creator | Decides between kernel-mode TUN (`createKernelTun`) or userspace gVisor TUN (`createGVisorTun`) | [`proxy/wireguard/config.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/wireguard/config.go) |
| `netBindServer` | Implements the bind side of the WireGuard device, handling packet rewriting and queue management | [`proxy/wireguard/server.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/wireguard/server.go) |

### Traffic Flow Through the System

1. **Initialization**: Xray reads the inbound configuration and calls `wireguard.NewServer` in [`proxy/wireguard/server.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/wireguard/server.go)

2. **Endpoint parsing**: The `parseEndpoints` function extracts IPv4/IPv6 endpoints and determines address families for DNS lookups

3. **TUN creation**: `conf.createTun()` returns a `tunCreator`. The system checks `IsClient`, `NoKernelTun`, and `KernelTunSupported()` to choose between kernel TUN or gVisor-based TUN

4. **Device building**: `tun.BuildDevice(createIPCRequest(conf), server.bindServer)` creates the WireGuard device and attaches the `netBindServer`

5. **Packet reception**: When UDP datagrams arrive, `Server.Process` parses the remote endpoint, wraps the connection into a `buf.PacketReader`, and pushes packets onto `bindServer.readQueue`

6. **Connection forwarding**: For each outgoing clear-text connection, `forwardConnection` builds a sub-context with inbound name **"wireguard"**, sets `inbound.Source` to the peer's address, and calls `dispatcher.DispatchLink` to 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:

```yaml
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: wireguard` activates the inbound implementation in [`proxy/wireguard/server.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/wireguard/server.go)
- `is_client: false` puts the inbound in server mode (required for inbound operation)
- `no_kernel_tun: false` allows automatic selection between kernel and gVisor TUN

### Multiple Peers Configuration

```yaml
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:

```go
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`](https://github.com/XTLS/Xray-core/blob/main/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`](https://github.com/XTLS/Xray-core/blob/main/proxy/wireguard/config.go) lines 33-53:

```go
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`](https://github.com/XTLS/Xray-core/blob/main/proxy/wireguard/server.go) | Core inbound implementation: `NewServer`, `Process`, `forwardConnection` | 36-70, 77-124, 26-71 |
| [`proxy/wireguard/config.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/wireguard/config.go) | TUN selection logic and configuration helpers | 33-53 |
| [`proxy/wireguard/tun_linux.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/wireguard/tun_linux.go) | Linux-specific kernel TUN implementation | Full file |
| [`proxy/wireguard/tun_default.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/wireguard/tun_default.go) | Fallback TUN implementations for other platforms | Full file |
| [`testing/scenarios/wireguard_test.go`](https://github.com/XTLS/Xray-core/blob/main/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`](https://github.com/XTLS/Xray-core/blob/main/proxy/wireguard/server.go), with `NewServer` handling initialization and `Server.Process` managing packet reception.

- **TUN selection** automatically chooses between kernel-mode TUN (better performance) and gVisor userspace TUN (better portability) based on `IsClient`, `NoKernelTun`, and `KernelTunSupported()` in [`proxy/wireguard/config.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/wireguard/config.go).

- **Configuration** uses `DeviceConfig` and `PeerConfig` defined in `proxy/wireguard/config.proto`, supporting YAML, JSON, and programmatic Go configuration.

- **Traffic forwarding** uses `forwardConnection` in [`proxy/wireguard/server.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/wireguard/server.go) to create properly tagged sub-contexts that integrate with Xray's `routing.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`](https://github.com/XTLS/Xray-core/blob/main/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`](https://github.com/XTLS/Xray-core/blob/main/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`](https://github.com/XTLS/Xray-core/blob/main/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.