netbirdio/netbird · error

clear and delete chain %s in table %s: %w

Error message

clear and delete chain %s in table %s: %w

What it means

Returned by router.cleanUpDefaultForwardRules when iptablesClient.ClearAndDeleteChain fails for an existing NetBird chain (flush + delete). The comment in the source names the classic cause: deleting a chain that is still referenced fails with 'device or resource busy'. The code removes its own jump rules first precisely to avoid this, so a failure means some other reference remains.

Source

Thrown at client/firewall/iptables/router_linux.go:431

	for _, chainInfo := range []struct {
		chain string
		table string
	}{
		{chainRTFWDIN, tableFilter},
		{chainRTFWDOUT, tableFilter},
		{chainRTPRE, tableMangle},
		{chainRTNAT, tableNat},
		{chainRTRDR, tableNat},
		{chainNATOutput, tableNat},
		{chainRTMSSCLAMP, tableMangle},
	} {
		ok, err := r.iptablesClient.ChainExists(chainInfo.table, chainInfo.chain)
		if err != nil {
			return fmt.Errorf("check chain %s in table %s: %w", chainInfo.chain, chainInfo.table, err)
		} else if ok {
			if err = r.iptablesClient.ClearAndDeleteChain(chainInfo.table, chainInfo.chain); err != nil {
				return fmt.Errorf("clear and delete chain %s in table %s: %w", chainInfo.chain, chainInfo.table, err)
			}
		}
	}

	return nil
}

func (r *router) createContainers() error {
	for _, chainInfo := range []struct {
		chain string
		table string
	}{
		{chainRTFWDIN, tableFilter},
		{chainRTFWDOUT, tableFilter},
		{chainRTPRE, tableMangle},
		{chainRTNAT, tableNat},
		{chainRTRDR, tableNat},
		{chainRTMSSCLAMP, tableMangle},

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. List references: sudo iptables-save | grep -e '-j NETBIRD-' and delete the foreign/stale jumps
  2. Retry cleanup once references are gone
  3. Ensure a single netbird instance runs per host
  4. If busy persists, flush manually: sudo iptables -F <chain> && sudo iptables -X <chain> per table

Example fix

# manual recovery when agent cleanup keeps failing
sudo iptables-save | grep -- '-j NETBIRD-'
sudo iptables -t nat -D OUTPUT -j NETBIRD-NAT-OUTPUT 2>/dev/null
sudo iptables -F NETBIRD-RT-NAT && sudo iptables -X NETBIRD-RT-NAT
Defensive patterns

Strategy: validation

Validate before calling

// before deleting, confirm nothing jumps into the chain
out, _ := exec.Command("iptables-save").Output()
for _, line := range strings.Split(string(out), "\n") {
	if strings.Contains(line, "-j "+chain) {
		return fmt.Errorf("chain %s still referenced: %s", chain, line)
	}
}

Try / catch

if err = r.iptablesClient.ClearAndDeleteChain(chainInfo.table, chainInfo.chain); err != nil {
	if strings.Contains(err.Error(), "busy") || strings.Contains(err.Error(), "Resource busy") {
		// sweep foreign jump rules, then retry this chain once
	}
	return fmt.Errorf("clear and delete chain %s in table %s: %w", chainInfo.chain, chainInfo.table, err)
}

Prevention

When it happens

Trigger: Init cleanup or Reset flushing one of the seven custom chains while something still jumps to it: a rule added by another tool or an older netbird version, a second NETBIRD chain referencing it, or a concurrent agent re-adding jumps between the jump cleanup and the chain delete.

Common situations: Upgrade from an older agent layout whose jump specs differ (so DeleteIfExists did not match); leftover hand-written rules targeting NETBIRD-* chains; two agents racing on one host.

Related errors


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