netbirdio/netbird · error

error removing peer %s from interface %s: %w

Error message

error removing peer %s from interface %s: %w

What it means

The first half of RemoveEndpointAddress (remove the peer entirely so it can be re-added without an endpoint) failed at the wgctrl configure call. Because the strategy is delete-then-re-add, any error here is safe: the peer is still on the device with its old endpoint, so connectivity state is unchanged and the operation can simply be retried. The underlying causes are the usual kernel-configure ones: device vanished, permissions, or netlink transport failure.

Source

Thrown at client/iface/configurer/kernel_unix.go:107

func (c *KernelConfigurer) RemoveEndpointAddress(peerKey string) error {
	peerKeyParsed, err := wgtypes.ParseKey(peerKey)
	if err != nil {
		return err
	}

	// Get the existing peer to preserve its allowed IPs
	existingPeer, err := c.getPeer(c.deviceName, peerKey)
	if err != nil {
		return fmt.Errorf("get peer: %w", err)
	}

	removePeerCfg := wgtypes.PeerConfig{
		PublicKey: peerKeyParsed,
		Remove:    true,
	}

	if err := c.configure(wgtypes.Config{Peers: []wgtypes.PeerConfig{removePeerCfg}}); err != nil {
		return fmt.Errorf(`error removing peer %s from interface %s: %w`, peerKey, c.deviceName, err)
	}

	//Re-add the peer without the endpoint but same AllowedIPs
	reAddPeerCfg := wgtypes.PeerConfig{
		PublicKey:         peerKeyParsed,
		AllowedIPs:        existingPeer.AllowedIPs,
		ReplaceAllowedIPs: true,
	}

	if err := c.configure(wgtypes.Config{Peers: []wgtypes.PeerConfig{reAddPeerCfg}}); err != nil {
		return fmt.Errorf(
			`error re-adding peer %s to interface %s with allowed IPs %v: %w`,
			peerKey, c.deviceName, existingPeer.AllowedIPs, err,
		)
	}

	return nil
}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Retry the whole RemoveEndpointAddress operation: no state was mutated, so the retry is safe
  2. Confirm the device still exists before retrying; recreate the interface if it was torn down intentionally
  3. Serialize peer mutations with a per-device lock to avoid netlink contention
  4. If it persists, capture wg show output to compare the device state against the configurer's view
Defensive patterns

Strategy: retry

Validate before calling

if _, err := net.InterfaceByName(c.deviceName); err != nil {
    return fmt.Errorf("device gone; skip removal: %w", err)
}

Type guard

func devicePresent(name string) bool {
    _, err := net.InterfaceByName(name)
    return err == nil
}

Try / catch

err := configurer.RemoveEndpointAddress(peerKey)
for attempt := 0; attempt < 2 && err != nil; attempt++ {
    if errors.Is(err, syscall.ENOENT) || errors.Is(err, syscall.EACCES) {
        break // permanent: surface it
    }
    time.Sleep(50 * time.Millisecond)
    err = configurer.RemoveEndpointAddress(peerKey) // remove step mutated nothing; safe to retry
}

Prevention

When it happens

Trigger: The WireGuard interface was removed between getPeer and the remove configure; losing CAP_NET_ADMIN mid-operation; netlink buffer exhaustion under heavy concurrent peer churn.

Common situations: Interface lifecycle races during engine restart; parallel peer updates from a fast-changing network map; agents on hosts where uapi sockets are intermittently unavailable (containerized or seccomp-restricted environments).

Related errors


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