netbirdio/netbird · error

apply firewall rule: %w

Error message

apply firewall rule: %w

What it means

Appended per-rule when DefaultManager's peer ACL application fails in protoRuleToFirewallRule or the AddPeerFiltering call beneath addInRules/addOutRules. Root causes visible through the %w chain: extractRuleIP failing to parse the rule's CIDR, `invalid port` when an old management sends the legacy single-port string and strconv.Atoi fails, `skipping firewall rule` for unknown protocol/action (typically a newer management than the agent), or the firewall backend (nftables/iptables) rejecting the rule. Failures are accumulated and logged as 'failed to apply N peer ACL rule(s)'; the affected rules are simply absent, and since the manager's default is deny, that traffic is blocked.

Source

Thrown at client/internal/acl/manager.go:180

	ipsetByRuleSelectors := make(map[string]string)

	// TODO: deny rules should be fatal: if a deny rule fails to apply, we must
	// roll back all allow rules to avoid a fail-open where allowed traffic bypasses
	// the missing deny. Currently we accumulate errors and continue.
	var merr *multierror.Error
	for _, r := range rules {
		// if this rule is member of rule selection with more than DefaultIPsCountForSet
		// it's IP address can be used in the ipset for firewall manager which supports it
		selector := d.getRuleGroupingSelector(r)
		ipsetName, ok := ipsetByRuleSelectors[selector]
		if !ok {
			d.ipsetCounter++
			ipsetName = fmt.Sprintf("nb%07d", d.ipsetCounter)
			ipsetByRuleSelectors[selector] = ipsetName
		}
		pairID, rulePair, err := d.protoRuleToFirewallRule(r, ipsetName)
		if err != nil {
			merr = multierror.Append(merr, fmt.Errorf("apply firewall rule: %w", err))
			continue
		}
		if len(rulePair) > 0 {
			d.peerRulesPairs[pairID] = rulePair
			newRulePairs[pairID] = rulePair
		}
	}

	if merr != nil {
		log.Errorf("failed to apply %d peer ACL rule(s): %v", merr.Len(), nberrors.FormatErrorOrNil(merr))
	}

	for pairID, rules := range d.peerRulesPairs {
		if _, ok := newRulePairs[pairID]; !ok {
			for _, rule := range rules {
				if err := d.firewall.DeletePeerRule(rule); err != nil {
					log.Errorf("failed to delete peer firewall rule: %v", err)
					continue

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Read the wrapped cause in the log line - each of the sub-errors has a distinct fix (parse vs backend)
  2. Align versions: update the agent to the management's version so new protocol/action enum values map correctly
  3. Verify the firewall backend: run `nft list ruleset` and check for NetBird chains; ensure the agent has NET_ADMIN
  4. Inspect the policy's rules (network map in debug logs) for malformed CIDRs or legacy port strings and fix them in management
Defensive patterns

Strategy: try-catch

Validate before calling

// management-side: validate rules before dispatching the network map
func validPeerRule(r *mgmProto.FirewallRule) bool {
    if _, err := netip.ParseAddr(strings.Split(r.Source, "/")[0]); r.Source != "" && err != nil {
        return false
    }
    switch r.Protocol {
    case mgmProto.RuleProtocol_TCP, mgmProto.RuleProtocol_UDP,
        mgmProto.RuleProtocol_ICMP, mgmProto.RuleProtocol_ALL:
    default:
        return false
    }
    return true
}

Try / catch

// the manager already aggregates; consume the multierror and alert per-rule
if err := mgr.ApplyPeerACLs(rules); err != nil {
    var merr *multierror.Error
    if errors.As(err, &merr) {
        for _, e := range merr.Errors {
            if strings.Contains(e.Error(), "invalid protocol") || strings.Contains(e.Error(), "skipping firewall rule") {
                log.Errorf("version skew: management sent unknown enum: %v", e)
            } else if strings.Contains(e.Error(), "add firewall rule") {
                log.Errorf("firewall backend rejected rule (nftables/iptables): %v", e)
            }
        }
    }
}

Prevention

When it happens

Trigger: Management/agent version skew introducing protocol enum values or port formats the old agent cannot map; nftables missing or ruleset locked (nft -f failing); malformed CIDR in a distribution group's address; ipset creation failing when a rule group exceeds DefaultIPsCountForSet.

Common situations: Upgraded self-hosted management with stale agents; hardened hosts without nft or with firewalld holding transactions; policies edited via API with unvalidated CIDRs.

Related errors


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