netbirdio/netbird · error

timeout when waiting for interface %s to be removed

Error message

timeout when waiting for interface %s to be removed

What it means

waitUntilRemoved() polls net.InterfaceByName every 100ms for up to 5 seconds and succeeds when the interface disappears (lookup fails with *net.OpError or returns nil). This error means the 5s deadline passed with the interface still present, or with InterfaceByName returning an error type the loop does not recognize as removal; Close() then falls back to Destroy().

Source

Thrown at client/iface/iface.go:346

	timeout := time.NewTimer(maxWaitTime)
	defer timeout.Stop()

	for {
		iface, err := net.InterfaceByName(w.Name())
		if err != nil {
			if _, ok := err.(*net.OpError); ok {
				log.Infof("interface %s has been removed", w.Name())
				return nil
			}
			log.Debugf("failed to get interface by name %s: %v", w.Name(), err)
		} else if iface == nil {
			log.Infof("interface %s has been removed", w.Name())
			return nil
		}

		select {
		case <-timeout.C:
			return fmt.Errorf("timeout when waiting for interface %s to be removed", w.Name())
		default:
			time.Sleep(100 * time.Millisecond)
		}
	}
}

// GetNet returns the netstack.Net for the netstack device
func (w *WGIface) GetNet() *netstack.Net {
	w.mu.Lock()
	defer w.mu.Unlock()

	return w.tun.GetNet()
}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Check what keeps the interface alive: `ip link show <name>` plus routes and firewall references
  2. Let Close()'s fallback Destroy() run; read its error if it also fails
  3. Remove manually (`ip link del`, `ifconfig destroy`, netsh) once the holder is cleared
  4. Wait for full teardown to finish before recreating the interface
Defensive patterns

Strategy: retry

Validate before calling

deadline := time.Now().Add(10 * time.Second)
for time.Now().Before(deadline) {
    if _, err := net.InterfaceByName(name); err != nil {
        return nil // removed
    }
    time.Sleep(200 * time.Millisecond)
}
return fmt.Errorf("interface %s still present after extended wait", name)

Prevention

When it happens

Trigger: Kernel interface still listed after tun.Close(): leftover routes or addresses keep it referenced, another process holds or recreates it, or a loaded host is slow to release it.

Common situations: Rapid down/up cycles; network management daemons re-adding the interface; zombie interfaces after unclean shutdowns.

Understand the failure class

Related errors


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