netbirdio/netbird · error

failed to delete rule: %s, %v: %w

Error message

failed to delete rule: %s, %v: %w

What it means

iptablesClient.Delete of the filter-table rule failed. go-iptables' Delete is not idempotent: deleting a rule that is already absent returns 'Bad rule (does a matching rule exist in that chain?)' as an error (exit 1), unlike DeleteIfExists used elsewhere in this file. Failure therefore usually means kernel/memory desync, the referenced ipset vanished so the match clause cannot resolve, the chain was removed, or a privilege/lock problem.

Source

Thrown at client/firewall/iptables/acl_linux.go:223

				return fmt.Errorf("delete ip from ipset: %w", err)
			}
			delete(ipsetList.ips, r.ip)
		}

		// if after delete, set still contains other IPs,
		// no need to delete firewall rule and we should exit here
		if len(ipsetList.ips) != 0 {
			return nil
		}

		// we delete last IP from the set, that means we need to delete
		// set itself and associated firewall rule too
		m.ipsetStore.deleteIpset(r.ipsetName)
		shouldDestroyIpset = true
	}

	if err := m.iptablesClient.Delete(tableName, r.chain, r.specs...); err != nil {
		return fmt.Errorf("failed to delete rule: %s, %v: %w", r.chain, r.specs, err)
	}

	if r.mangleSpecs != nil {
		if err := m.iptablesClient.Delete(tableMangle, chainRTPRE, r.mangleSpecs...); err != nil {
			log.Errorf("failed to delete mangle rule: %v", err)
		}
	}

	if shouldDestroyIpset {
		if err := m.destroyIPSet(r.ipsetName); err != nil {
			if errors.Is(err, ipset.ErrBusy) || errors.Is(err, ipset.ErrSetNotExist) {
				log.Debugf("destroy empty ipset: %v", err)
			} else {
				log.Errorf("destroy empty ipset: %v", err)
			}
		}
	}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Use iptablesClient.DeleteIfExists for the ACL rule deletion, mirroring the mangle path in the same function.
  2. When keeping Delete, match the go-iptables *Error with ExitStatus()==1 and treat it as success.
  3. Reconcile state via Reset before retrying deletes after an unclean shutdown.
  4. Ensure root and no concurrent xtables.lock holders.

Example fix

// before
if err := m.iptablesClient.Delete(tableName, r.chain, r.specs...); err != nil {
    return fmt.Errorf("failed to delete rule: %s, %v: %w", r.chain, r.specs, err)
}

// after
if err := m.iptablesClient.DeleteIfExists(tableName, r.chain, r.specs...); err != nil {
    return fmt.Errorf("failed to delete rule: %s, %v: %w", r.chain, r.specs, err)
}
Defensive patterns

Strategy: fallback

Validate before calling

// idempotent pre-check: only call Delete when the rule is present
if ok, err := iptClient.Exists(tableName, r.chain, r.specs...); err == nil && !ok {
    // rule already absent; skip the delete and proceed to ipset teardown
}

Try / catch

if err := mgr.DeletePeerRule(rule); err != nil {
    var ee *iptables.Error
    if errors.As(err, &ee) && ee.ExitStatus() == 1 && strings.Contains(ee.Error(), "Bad rule") {
        // rule already gone: idempotent success
        err = nil
    }
}

Prevention

When it happens

Trigger: DeletePeerRule when the kernel rule was already removed externally but the manager still tracks it; deleting the last IP of a set whose earlier partial cleanup removed the rule; another process holding the xtables lock; non-root execution.

Common situations: Double-invoked cleanup (caller retries after a timeout, first attempt actually succeeded); external firewall flushing between manager start and delete; crash recovery where persisted rules no longer match the kernel.

Related errors


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