slackhq/nebula · critical

can not load the wintun driver

Error message

can not load the wintun driver: %w

What it means

newTun on Windows requires the wintun.dll driver. checkWinTunExists() tries to load wintun.dll; when that fails, nebula wraps the cause as 'can not load the wintun driver'. Without wintun.dll the TUN device cannot be created and nebula cannot start.

Solutions

  1. Download the official wintun distribution and place the architecture-correct wintun.dll next to nebula.exe (or in PATH).
  2. Verify DLL architecture matches the nebula binary (x64/x86/arm64).
  3. Check antivirus/EDR quarantine logs and whitelist wintun.dll.
  4. Ensure the wintun.dll version is one supported by your nebula release and not corrupted (re-download and compare checksums).

Example fix

// before
nebula.exe in C:\nebula\ with no wintun.dll anywhere on PATH
// after
C:\nebula\
  nebula.exe
  wintun.dll   (amd64 build matching the nebula binary)
Defensive patterns

Strategy: validation

Validate before calling

if _, err := os.Stat(filepath.Join(exeDir, "wintun.dll")); err != nil {
	return errors.New("wintun.dll missing next to nebula.exe; install the official wintun distribution")
}

Try / catch

tun, err := overlay.NewTun(cfg, logger, prefixes, false)
if err != nil && strings.Contains(err.Error(), "wintun driver") {
	// surface install guidance: correct-arch wintun.dll beside the binary
	return fmt.Errorf("install wintun: %w", err)
}

Prevention

When it happens

Trigger: newTun calls checkWinTunExists() and LoadLibrary/finds no wintun.dll on the search path, an architecture-mismatched DLL (e.g. 32-bit DLL with 64-bit nebula), or a corrupted/incompatible wintun version.

Common situations: wintun.dll missing next to nebula.exe or in PATH; wrong architecture (x86 vs x64 vs arm64) DLL; antivirus quarantining wintun.dll; running from a stripped-down environment where the DLL wasn't bundled.

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/0ad4a5c6b722218f. Report an issue: GitHub.

Appendix: source

Thrown at overlay/tun_windows.go:61

	bypassWDF       bool
	wdfBypass       closer
	l               *slog.Logger

	tun *wintun.NativeTun
}

func (t *winTun) Read(b []byte) (int, error) {
	return t.tun.Read(b, 0)
}

func newTunFromFd(_ *config.C, _ *slog.Logger, _ int, _ []netip.Prefix) (Device, error) {
	return nil, fmt.Errorf("newTunFromFd not supported in Windows")
}

func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*winTun, error) {
	err := checkWinTunExists()
	if err != nil {
		return nil, fmt.Errorf("can not load the wintun driver: %w", err)
	}

	deviceName := c.GetString("tun.dev", "")
	guid, err := generateGUIDByDeviceName(deviceName)
	if err != nil {
		return nil, fmt.Errorf("generate GUID failed: %w", err)
	}

	cat, setCat, err := parseNetworkCategory(c.GetString("tun.network_category", "private"))
	if err != nil {
		return nil, err
	}

	t := &winTun{
		Device:          deviceName,
		vpnNetworks:     vpnNetworks,
		MTU:             c.GetInt("tun.mtu", DefaultMTU),
		guid:            *guid,

View on GitHub (pinned to dd8f660c0a)