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
- Use iptablesClient.DeleteIfExists for the ACL rule deletion, mirroring the mangle path in the same function.
- When keeping Delete, match the go-iptables *Error with ExitStatus()==1 and treat it as success.
- Reconcile state via Reset before retrying deletes after an unclean shutdown.
- 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
- Prefer the manager's DeleteIfExists-style paths (or contribute one for the ACL rule) so deletes are idempotent.
- Do not retry delete calls blindly after timeouts; check Exists first since the first attempt often succeeded.
- Reconcile manager state with Reset after crashes instead of replaying stale deletes.
- Keep external firewall manipulation off hosts running the agent.
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
- rule already exists
- add IP to ipset: %w
- failed to check rule: %w
- delete ip from ipset: %w
- list chains: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/e1017cbffbafa4b0.
Report an issue: GitHub.