tailscale/tailscale · critical

unable to create tuntap device file: %w

Error message

unable to create tuntap device file: %w

What it means

In kernel mode (TS_USERSPACE unset/false), containerboot ensures /dev/net/tun exists, creating /dev/net via MkdirAll and the device node via unix.Mknod(major 10, minor 200) if missing. Failure of either filesystem operation becomes this fatal error before tailscaled starts. It is purely a container-capability/filesystem problem, not a Kubernetes API one.

Source

Thrown at cmd/containerboot/main.go:337

}

func main() {
	if err := run(); err != nil && !errors.Is(err, context.Canceled) {
		log.Fatal(err)
	}
}

func run() error {
	log.SetPrefix("boot: ")

	cfg, err := configFromEnv()
	if err != nil {
		return fmt.Errorf("invalid configuration: %w", err)
	}

	if !cfg.UserspaceMode {
		if err := ensureTunFile(cfg.Root); err != nil {
			return fmt.Errorf("unable to create tuntap device file: %w", err)
		}
		if cfg.ProxyTargetIP != "" || cfg.ProxyTargetDNSName != "" || cfg.Routes != nil || cfg.TailnetTargetIP != "" || cfg.TailnetTargetFQDN != "" {
			if err := ensureIPForwarding(cfg.Root, cfg.ProxyTargetIP, cfg.TailnetTargetIP, cfg.TailnetTargetFQDN, cfg.Routes); err != nil {
				log.Printf("Failed to enable IP forwarding: %v", err)
				log.Printf("To run tailscale as a proxy or router container, IP forwarding must be enabled.")
				if cfg.InKubernetes {
					return fmt.Errorf("you can either set the sysctls as a privileged initContainer, or run the tailscale container with privileged=true.")
				} else {
					return fmt.Errorf("you can fix this by running the container with privileged=true, or the equivalent in your container runtime that permits access to sysctls.")
				}
			}
		}
	}

	// Root context for the whole containerboot process, used to make sure
	// shutdown signals are promptly and cleanly handled.
	ctx, cancel := contextWithExitSignalWatch()
	defer cancel()

View on GitHub (pinned to cfe32b8be6)

Solutions

  1. Set TS_USERSPACE=true if TUN networking is not required (userspace mode needs no device)
  2. Run the container privileged (operator default for proxies) or grant CAP_MKNOD and CAP_NET_ADMIN
  3. Bind-mount the host device instead of creating it: devices: [{path: /dev/net/tun}] in the container spec
  4. Verify in-container: ls -l /dev/net/tun after applying the fix

Example fix

# before
# unprivileged pod, kernel mode -> mknod fails
TS_USERSPACE unset
securityContext: {runAsNonRoot: true}

# after (option 1: userspace mode, no tun needed)
env: [{name: TS_USERSPACE, value: "true"}]

# after (option 2: keep kernel mode, provide the device)
securityContext:
  capabilities: {add: ["NET_ADMIN", "MKNOD"]}
volumes: [{hostPath: {path: /dev/net/tun}, name: tun}]
volumeMounts: [{mountPath: /dev/net/tun, name: tun}]
Defensive patterns

Strategy: validation

Validate before calling

// Skip device creation when it cannot succeed, choosing userspace mode up front
privileged := detectCapabilities() // e.g. check CAP_MKNOD in /proc/self/status CapEff
if !privileged && def.Bool(os.Getenv("TS_USERSPACE"), true) == false {
	return errors.New("kernel mode requires CAP_MKNOD/NET_ADMIN or a mounted /dev/net/tun — set TS_USERSPACE=true or fix securityContext")
}

Type guard

func tunAvailable(root string) bool {
	_, err := os.Stat(filepath.Join(root, "dev/net/tun"))
	return err == nil
}

Try / catch

if err := ensureTunFile(cfg.Root); err != nil {
	// permanent for this securityContext — do not retry; either fix caps/mount or switch to userspace mode
	return fmt.Errorf("unable to create tuntap device file: %w", err)
}

Prevention

When it happens

Trigger: unix.Mknod or os.MkdirAll failing when the container lacks CAP_MKNOD (unprivileged Pod), the root filesystem is read-only, a securityProfile (runAsNon-root, seccomp, AppArmor) blocks device creation, or /dev/net already exists but /dev is a ro mount.

Common situations: Custom ProxyClass or hand-written manifest removing privileged:true without adding CAP_MKNOD+CAP_NET_ADMIN; hardened nodes (SELinux denying mknod); running the image in docker/podman without --privileged and without a host /dev/net/tun bind-mount.

Related errors


AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15). Data as JSON: /api/errors/cb1e1f60d951e886. Report an issue: GitHub.