XTLS/Xray-core · error
invalid xray.tun.fd: file descriptor is not a TUN device
Error message
invalid xray.tun.fd: file descriptor is not a TUN device
What it means
Xray validates a user-supplied TUN file descriptor (passed via the xray.tun.fd platform setting) by issuing the TUNGETIFF ioctl and checking the returned flags. If the IFF_TUN bit is not set, the fd is not a TUN interface (it may be a socket, pipe, or regular file), so startup aborts. This is a configuration/integration guard: the fd must come from opening /dev/net/tun and binding it with TUNSETIFF.
Source
Thrown at proxy/tun/tun_linux.go:97
fd, err := strconv.Atoi(fdStr)
if err != nil {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey).Base(err)
}
if fd < 3 {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": file descriptor must be >= 3")
}
ifr, err := unix.NewIfreq("")
if err != nil {
return -1, nil, true, err
}
if err = unix.IoctlIfreq(fd, unix.TUNGETIFF, ifr); err != nil {
return -1, nil, true, err
}
flags := ifr.Uint16()
if flags&unix.IFF_TUN == 0 {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": file descriptor is not a TUN device")
}
if flags&unix.IFF_NO_PI == 0 {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": TUN device must use IFF_NO_PI")
}
actualName := ifr.Name()
if expectedName != "" && actualName != expectedName {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": TUN device name ", actualName, " does not match configured name ", expectedName)
}
tunLink, err := netlink.LinkByName(actualName)
if err != nil {
return -1, nil, true, err
}
if err = unix.SetNonblock(fd, true); err != nil {
return -1, nil, true, err
}View on GitHub (pinned to 7d214f8b09)
Solutions
- Verify the fd with a TUNGETIFF ioctl before passing it to Xray (see validationCode)
- Create the TUN yourself via open("/dev/net/tun", O_RDWR) + ioctl(TUNSETIFF, IFF_TUN|IFF_NO_PI) and pass that fd
- Remove xray.tun.fd entirely and let Xray create and name the TUN device itself via the tun inbound config
- Double-check the fd number actually corresponds to /dev/net/tun in /proc/<pid>/fd (it should be a symlink to /dev/net/tun)
Example fix
// before: passing an arbitrary fd
export XRAY_TUN_FD=5 // fd 5 is a UDP socket
// after: create a real TUN fd first
fd, _ := unix.Open("/dev/net/tun", unix.O_RDWR, 0)
ifr, _ := unix.NewIfreq("tun0")
ifr.SetUint16(unix.IFF_TUN | unix.IFF_NO_PI)
_ = unix.IoctlIfreq(fd, unix.TUNSETIFF, ifr)
// now export/hand over this fd Defensive patterns
Strategy: validation
Validate before calling
func isTunFD(fd int) bool {
ifr, err := unix.NewIfreq("")
if err != nil {
return false
}
if err := unix.IoctlIfreq(fd, unix.TUNGETIFF, ifr); err != nil {
return false
}
return ifr.Uint16()&unix.IFF_TUN != 0
}
// before starting xray with xray.tun.fd:
if !isTunFD(myFd) {
log.Fatal("fd is not a TUN device")
} Try / catch
if err := tun.Start(); err != nil {
if strings.Contains(err.Error(), "not a TUN device") {
// fix the fd source (open /dev/net/tun + TUNSETIFF) and retry once
}
} Prevention
- Always create the fd via open("/dev/net/tun") + TUNSETIFF in the same process that hands it over
- Confirm /proc/<pid>/fd/<n> links to /dev/net/tun before export
- Prefer letting xray create the TUN itself unless fd passing is required
When it happens
Trigger: Calling Xray with xray.tun.fd pointing at a UDP/TCP socket, an fd inherited from a parent process that is not /dev/net/tun, or a raw IP socket. Also opening /dev/net/tun but never calling TUNSETIFF before handing the fd over.
Common situations: Integration with an external TUN manager (e.g. a supervisor script or another VPN stack) that passes the wrong fd number; shell scripts that dup fds; Android/iOS handoff where the fd is actually a VpnService socket rather than a TUN; stale fd numbers after process restart.
Related errors
- invalid xray.tun.fd: TUN device must use IFF_NO_PI
- invalid xray.tun.fd: TUN device name {actualName} does not m
- invalid interface address {address}
- invalid system route {cidr}
- outbound interface cannot be the TUN interface
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/6207787d23fc5d28.
Report an issue: GitHub.