netbirdio/netbird · warning

rule already exists

Error message

rule already exists

What it means

Not a syscall failure: iptablesClient.Exists returned true, meaning a rule with exactly these specs (including the -m set --match-set clause) is already programmed in NETBIRD-ACL-INPUT, so the manager refuses a duplicate. It typically surfaces when kernel rules survived an unclean agent shutdown while the in-memory ipsetStore did not, so the code takes the create-set path and then discovers the leftover filter rule, or when the same peer/rule is applied twice without deduplication.

Source

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

			}
		}
		if err := m.createIPSet(ipsetName); err != nil {
			return nil, fmt.Errorf("create ipset: %w", err)
		}
		if err := m.addToIPSet(ipsetName, ip); err != nil {
			return nil, fmt.Errorf("add IP to ipset: %w", err)
		}

		ipList := newIpList(ip.String())
		m.ipsetStore.addIpList(ipsetName, ipList)
	}

	ok, err := m.iptablesClient.Exists(tableFilter, chain, specs...)
	if err != nil {
		return nil, fmt.Errorf("failed to check rule: %w", err)
	}
	if ok {
		return nil, fmt.Errorf("rule already exists")
	}

	// Insert DROP rules at the beginning, append ACCEPT rules at the end
	if action == firewall.ActionDrop {
		// Insert at the beginning of the chain (position 1)
		err = m.iptablesClient.Insert(tableFilter, chain, 1, specs...)
	} else {
		err = m.iptablesClient.Append(tableFilter, chain, specs...)
	}
	if err != nil {
		return nil, err
	}

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

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Call aclManager.Reset() (or Manager.Reset) before reprogramming rules after a restart, which is what init/cleanChains normally does; ensure that path ran.
  2. Deduplicate at the caller: track rule IDs and skip re-adding an unchanged rule.
  3. Treat Exists==true as idempotent success returning a Rule handle instead of an error.

Example fix

// before
if ok {
    return nil, fmt.Errorf("rule already exists")
}

// after
if ok {
    return []firewall.Rule{&Rule{
        ruleID:    uuid.New().String(),
        ipsetName: ipsetName,
        ip:        ip.String(),
        chain:     chain,
        specs:     specs,
        v6:        m.v6,
    }}, nil
}
Defensive patterns

Strategy: validation

Validate before calling

// dedupe before adding: query the manager for the kernel rule the same way it does
if ok, err := iptClient.Exists("filter", "NETBIRD-ACL-INPUT", specs...); err == nil && ok {
    // rule already programmed; skip AddPeerFiltering or adopt it instead of erroring
    return existingRuleHandle()
}

Try / catch

if err := mgr.AddPeerFiltering(...); err != nil {
    if strings.Contains(err.Error(), "rule already exists") {
        // benign duplicate: desired state already holds, log and continue
        log.Warn("ACL rule already present, skipping")
        err = nil
    }
}

Prevention

When it happens

Trigger: Agent crashed or was SIGKILLed so iptables rules persisted, then re-added on next start before Reset/cleanChains removed them; duplicate AddPeerFiltering calls for the same ipset, ip, port, and action; two ACL entries from the management network map collapsing to identical specs.

Common situations: Recovery flows after `kill -9` or power loss; network-map re-application where a peer's rule is resent unchanged; test harnesses that re-run AddPeerFiltering without resetting the manager.

Related errors


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