# How to Configure Transparent Proxy (TUN Mode) in Xray-core on Linux, Windows, macOS, FreeBSD, Android, and iOS

> Learn to configure Xray-core transparent proxy TUN mode across Linux, Windows, macOS, FreeBSD, Android, and iOS. Set up virtual network interfaces and avoid routing loops with this guide.

- 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

---

**To configure transparent proxy TUN mode in Xray-core, define a `tun` inbound in your JSON configuration, instantiate the platform-specific virtual network interface via the internal drivers in `proxy/tun/`, and ensure Xray's own upstream traffic bypasses the TUN device to avoid routing loops.**

Xray-core provides a **TUN inbound** that creates a virtual network interface to capture raw IP packets, enabling system-wide transparent proxying at layer 3. This article explains how to configure transparent proxy TUN mode in Xray-core across all supported platforms using the specific implementation files found in the XTLS/Xray-core repository. Unlike traditional TCP/UDP inbounds, TUN mode operates on IP packets directly, requiring platform-specific setup for interface creation and routing rules.

## Core Architecture of TUN Mode

The TUN implementation in Xray-core abstracts platform differences behind a common configuration interface while using OS-specific drivers to create the virtual device.

### Configuration Mapping

In [`infra/conf/tun.go`](https://github.com/XTLS/Xray-core/blob/main/infra/conf/tun.go), the `TunConfig` struct maps JSON configuration fields to the internal `tun.Config` used by the proxy engine. This configuration specifies interface names, MTU values, gateway addresses, and DNS servers that the TUN device will advertise.

### Platform Abstraction Layer

The generic `Tun` interface defined in [`proxy/tun/tun.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/tun/tun.go) provides the contract that all platform-specific implementations must satisfy. Each operating system provides its own driver file that creates the actual network device:

- **Linux**: [`proxy/tun/tun_linux.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/tun/tun_linux.go) uses the `TUNSETIFF` ioctl to allocate `tunX` devices
- **Windows**: [`proxy/tun/tun_windows.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/tun/tun_windows.go) wraps the Wintun library and requires `wintun.dll`
- **macOS/iOS**: [`proxy/tun/tun_darwin.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/tun/tun_darwin.go) creates `utun` devices via the kernel API
- **FreeBSD**: [`proxy/tun/tun_freebsd.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/tun/tun_freebsd.go) uses the `tun(4)` character device
- **Android**: [`proxy/tun/tun_android.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/tun/tun_android.go) receives a file descriptor from the Android `VpnService`

Detailed routing considerations and design limitations are documented in [`proxy/tun/README.md`](https://github.com/XTLS/Xray-core/blob/main/proxy/tun/README.md), which provides essential guidance for preventing traffic loops.

## Linux TUN Configuration

On Linux, Xray-core uses the `TUNSETIFF` ioctl system call to create a TUN device, as implemented in [`proxy/tun/tun_linux.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/tun/tun_linux.go).

After starting Xray with the TUN inbound configured, bring the interface online and configure routing:

**Systemd-networkd configuration (`/etc/systemd/network/90-xray0.network`):**

```ini
[Match]
Name = xray0

[Network]
KeepConfiguration = yes

[Link]
ActivationPolicy = manual
RequiredForOnline = no

[Route]
Table = 1001
Destination = 0.0.0.0/0

[RoutingPolicyRule]
From = 192.168.0.0/24
Table = 1001

```

**Manual routing commands:**

```bash

# Bring the interface up

ip link set dev xray0 up

# Prevent routing loops by excluding Xray's uplink IP (e.g., 203.0.113.5)

ip route add 203.0.113.5/32 via 192.168.0.1

# Route LAN traffic through the TUN table

ip rule add from 192.168.0.0/24 table 1001

```

## Windows TUN Configuration

Windows implementations rely on the Wintun driver ([`proxy/tun/tun_windows.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/tun/tun_windows.go)). Place `wintun.dll` in the same directory as `xray.exe` before starting the service.

Once Xray initializes, a new network adapter appears with the configured name. Add an on-link route using the adapter's interface index:

```cmd
rem Discover the adapter index (e.g., 47) via "route print"
route add 1.1.1.1 mask 255.255.255.0 0.0.0.0 if 47

```

Windows routes traffic at layer 3 through this adapter, but you must still ensure Xray's own outbound connections bypass the TUN interface to prevent loops.

## macOS and iOS TUN Configuration

Both macOS and iOS use [`proxy/tun/tun_darwin.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/tun/tun_darwin.go) to create `utun` devices via the kernel's `utun` API.

**macOS setup:**

```bash

# Create the interface (handled by Xray) then add routes

sudo route add -net 10.0.0.0/24 -iface utun3

```

**iOS setup:**

iOS requires obtaining the file descriptor from a `NetworkExtension` `packetFlow` and passing it to Xray via environment variables. The application embedding Xray must set `XRAY_TUN_FD` (or `xray.tun.fd`, as the name is case-insensitive) before launching the binary:

```swift
// Swift example using NetworkExtension
let fd = packetFlow.value(forKeyPath: "socket.fileDescriptor") as! Int32
setenv("XRAY_TUN_FD", String(fd), 1)
// Launch Xray-core via gomobile framework

```

## FreeBSD TUN Configuration

FreeBSD utilizes the `tun(4)` driver as implemented in [`proxy/tun/tun_freebsd.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/tun/tun_freebsd.go). After Xray creates the device node, assign addresses and routes:

```bash

# Assign IP to the tun device

ifconfig tun0 inet 169.254.10.1/30 up

# Add routes through the interface

route add -net 0.0.0.0/0 -iface tun0

```

## Android TUN Configuration

Android implementations in [`proxy/tun/tun_android.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/tun/tun_android.go) do not create the TUN device directly. Instead, the application must establish a `VpnService`, obtain the file descriptor, and communicate it to Xray-core via the process environment.

**Kotlin implementation:**

```kotlin
val tunInterface = vpnServiceBuilder.establish()
System.setProperty("XRAY_TUN_FD", tunInterface?.fd?.toString() ?: "")
// Launch Xray as subprocess or via JNI

```

The environment variable `XRAY_TUN_FD` (also recognized as `xray.tun.fd`) must contain the integer file descriptor value before Xray initializes its inbound handlers.

## Routing and Loop Prevention

Because TUN mode operates at layer 3, traffic enters Xray as raw IP packets after the OS has performed DNS resolution. This creates two critical requirements:

1. **DNS handling**: DNS queries resolve through the system's normal resolver before packets enter the TUN device. Xray sees IP addresses, not domain names, for outbound routing decisions unless you use Xray's internal DNS for the initial connection.

2. **Loop prevention**: You must ensure Xray's upstream traffic (connections to your proxy servers) routes through the physical gateway, not the TUN interface. Common strategies include:
   - Adding static host routes for proxy server IPs via the real gateway
   - Using policy-based routing (Linux `ip-rule`, `iptables` marks) to exclude Xray process traffic from the TUN table

## Summary

- **TUN mode** creates a virtual network interface at layer 3 to capture all IP traffic system-wide, unlike TCP/UDP inbounds that listen on specific ports.
- **Configuration** uses the `tun` protocol in [`infra/conf/tun.go`](https://github.com/XTLS/Xray-core/blob/main/infra/conf/tun.go) with settings for name, MTU, gateway, and DNS.
- **Platform drivers** reside in `proxy/tun/` and use `TUNSETIFF` (Linux), Wintun (Windows), `utun` (Darwin), `tun(4)` (FreeBSD), or file descriptors (Android/iOS).
- **Mobile platforms** require passing the VPN file descriptor via the `XRAY_TUN_FD` environment variable before starting Xray.
- **Routing safety** demands that Xray's own upstream traffic bypass the TUN interface to prevent infinite loops.

## Frequently Asked Questions

### What is the difference between TUN mode and regular inbound proxies in Xray-core?

Regular inbounds like `socks` or `http` listen on TCP/UDP ports and handle application-layer traffic, requiring explicit proxy configuration in client applications. TUN mode operates at layer 3 via [`proxy/tun/tun.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/tun/tun.go), creating a virtual network interface that intercepts all IP packets transparently without application support, enabling system-wide proxying for UDP, TCP, and ICMP traffic.

### How do I prevent routing loops when using TUN mode?

Routing loops occur when Xray's encrypted traffic to upstream servers re-enters the TUN interface. Prevent this by adding static routes for your proxy server IPs via your physical gateway (e.g., `ip route add <server-ip>/32 via <gateway>` on Linux), or use policy routing to mark and bypass TUN traffic originating from the Xray process itself, as detailed in [`proxy/tun/README.md`](https://github.com/XTLS/Xray-core/blob/main/proxy/tun/README.md).

### Why does DNS resolution behave differently in TUN mode?

In TUN mode, DNS resolution happens **outside** Xray-core because the OS resolver performs lookups before IP packets enter the virtual interface. Xray receives only IP addresses, not domain names, which affects routing rules based on domains. To control DNS, configure the OS resolver or use Xray's internal DNS client for its own outbound connections, but expect that forwarded client traffic arrives pre-resolved.

### Can I use TUN mode on iOS without jailbreaking?

Yes. Non-jailbroken iOS apps can use TUN mode through the `NetworkExtension` framework. Your app creates a `NEPacketTunnelProvider`, obtains the file descriptor from `packetFlow`, and passes it to Xray-core via the `XRAY_TUN_FD` environment variable before launching the engine through a gomobile-generated framework. This uses the same [`proxy/tun/tun_darwin.go`](https://github.com/XTLS/Xray-core/blob/main/proxy/tun/tun_darwin.go) implementation as macOS but requires proper entitlements from Apple.