slackhq/nebula · error
created /dev/net/tun, but still failed: %w
Error message
created /dev/net/tun, but still failed: %w
What it means
openTunDev (overlay/tun_linux.go:101), after successfully creating /dev/net/tun via mknod, opens it read-write. This error wraps an open failure that occurs despite the node existing, meaning the TUN device is unusable even after provisioning.
Source
Thrown at overlay/tun_linux.go:101
// openTunDev opens /dev/net/tun, creating the device node first if it's
// missing (docker containers occasionally omit it).
func openTunDev() (int, error) {
fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0)
if err == nil {
return fd, nil
}
if !os.IsNotExist(err) {
return -1, err
}
if err = os.MkdirAll("/dev/net", 0755); err != nil {
return -1, fmt.Errorf("/dev/net/tun doesn't exist, failed to mkdir -p /dev/net: %w", err)
}
if err = unix.Mknod("/dev/net/tun", unix.S_IFCHR|0600, int(unix.Mkdev(10, 200))); err != nil {
return -1, fmt.Errorf("failed to create /dev/net/tun: %w", err)
}
fd, err = unix.Open("/dev/net/tun", os.O_RDWR, 0)
if err != nil {
return -1, fmt.Errorf("created /dev/net/tun, but still failed: %w", err)
}
return fd, nil
}
// tunSetIff runs TUNSETIFF with the given flags and returns the kernel-chosen device name on success.
func tunSetIff(fd int, name string, flags uint16) (string, error) {
var req ifReq
req.Flags = flags
copy(req.Name[:], name)
if err := ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
return "", err
}
return strings.Trim(string(req.Name[:]), "\x00"), nil
}
// tsoOffloadFlags are the TUN_F_* bits we ask the kernel to enable when a TSO-capable TUN is available.
const tsoOffloadFlags = unix.TUN_F_CSUM | unix.TUN_F_TSO4 | unix.TUN_F_TSO6 | unix.TUN_F_TSO_ECN
View on GitHub (pinned to dd8f660c0a)
Solutions
- Load the kernel module: modprobe tun (and ensure it loads at boot).
- Verify kernel support: CONFIG_TUN=y/m; check 'ls /sys/class/misc/tun'.
- Fix permissions/ownership on /dev/net/tun so the running user can open it O_RDWR.
- Grant the container access to the device (--device /dev/net/tun) and CAP_NET_ADMIN for the subsequent TUNSETIFF ioctl.
Example fix
// before (module missing) $ ls /dev/net/tun # node created but open fails // after $ modprobe tun $ docker run --device /dev/net/tun --cap-add NET_ADMIN nebula-image
Defensive patterns
Strategy: validation
Validate before calling
if _, err := os.Stat("/sys/class/misc/tun"); os.IsNotExist(err) {
return errors.New("kernel lacks TUN support; load the tun module (modprobe tun)")
}
if err := unix.Access("/dev/net/tun", unix.W_OK); err != nil {
return fmt.Errorf("no write access to /dev/net/tun: %w", err)
} Try / catch
fd, err := openTunDev()
if err != nil && strings.Contains(err.Error(), "created /dev/net/tun, but still failed") {
// kernel/module or permission issue; prompt operator to modprobe tun or fix perms
} Prevention
- Ensure CONFIG_TUN is enabled and the tun module loads at boot on hosts.
- Verify /dev/net/tun permissions allow O_RDWR for the running user.
- In containers, mount the real device so kernel-backed access is guaranteed.
- Smoke-test 'tun' availability in host/instance provisioning before deploying nebula.
When it happens
Trigger: unix.Open("/dev/net/tun", O_RDWR, 0) fails after mknod succeeded: the kernel lacks tun support (tun module not loaded/built in), EACCES on device permissions, or ENODEV/ENXIO when the kernel has no TUN driver.
Common situations: Minimal kernels or VPS hosts without the tun kernel module (modprobe tun fails); hardened containers granting mknod but denying device access; stale/mismatched device node permissions (0600 owned by another user).
Related errors
- /dev/net/tun doesn't exist, failed to mkdir -p /dev/net: %w
- failed to create /dev/net/tun: %w
- failed to bring the tun device up: %s
- failed to enable offload on multiqueue tun fd: %w
- failed to get tun address list: %s
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/de0f83527785ddc9.
Report an issue: GitHub.