netbirdio/netbird · error

failed to remove WireGuard interface %s: %w

Error message

failed to remove WireGuard interface %s: %w

What it means

Emitted at the end of WGIface.Close() when waitUntilRemoved() timed out (interface still listed after 5 seconds of 100ms polling) and the forced w.Destroy() that follows also failed. It is a compounded teardown failure: neither graceful removal nor the platform-specific destroy cleared the interface, so it outlives the agent process.

Source

Thrown at client/iface/iface.go:240

	// wireguard-go device.Close() waits for its send/receive goroutines
	// to drain. Some of those goroutines re-enter WGIface methods that
	// take w.mu (e.g. the packet filter DNS hook calls GetDevice()), so
	// holding the mutex here would deadlock the shutdown path.
	tun := w.tun
	w.mu.Unlock()

	if err := tun.Close(); err != nil {
		result = multierror.Append(result, fmt.Errorf("failed to close wireguard interface %s: %w", w.Name(), err))
	}

	if nbnetstack.IsEnabled() {
		return errors.FormatErrorOrNil(result)
	}

	if err := w.waitUntilRemoved(); err != nil {
		log.Warnf("failed to remove WireGuard interface %s: %v", w.Name(), err)
		if err := w.Destroy(); err != nil {
			result = multierror.Append(result, fmt.Errorf("failed to remove WireGuard interface %s: %w", w.Name(), err))
			return errors.FormatErrorOrNil(result)
		}
		log.Infof("interface %s successfully removed", w.Name())
	}

	return errors.FormatErrorOrNil(result)
}

// SetFilter sets packet filters for the userspace implementation
func (w *WGIface) SetFilter(filter device.PacketFilter) error {
	w.mu.Lock()
	defer w.mu.Unlock()

	if w.tun.FilteredDevice() == nil {
		return fmt.Errorf("userspace packet filtering not handled on this device")
	}

	w.filter = filter

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Identify the holder: `ip link show <name>` / `ifconfig <name>` / `netsh interface show interface`, plus routes referencing it
  2. Remove manually: `sudo ip link del <name>` (Linux), `sudo ifconfig <name> destroy` (BSD), adapter removal in Device Manager (Windows)
  3. Fix the underlying Destroy() failure using its own error message (wrapped in this chain)
  4. Reboot the host if the kernel refuses all removal
Defensive patterns

Strategy: fallback

Validate before calling

if _, err := net.InterfaceByName(name); err == nil {
    // still present after Close: fall back to platform-specific destroy
    if err := destroyInterface(name); err != nil {
        log.Warnf("manual cleanup required for %s: %v", name, err)
    }
}

Prevention

When it happens

Trigger: Kernel-mode interface lingering because routes, addresses, or another process still reference it, combined with Destroy() failing for its own per-OS reason (ifconfig destroy / netlink LinkDel / netsh); only reached when netstack is disabled.

Common situations: Rapid up/down cycles leaving zombie interfaces; external network or firewall daemons holding references; degraded environments where the destroy command cannot execute.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/0b0234d0de1f1a7c. Report an issue: GitHub.