netbirdio/netbird · error

insert established rule: %w

Error message

insert established rule: %w

What it means

Returned by router.createContainers when insertEstablishedRule(NETBIRD-RT-FWD-OUT) fails: identical INSERT of the conntrack established rule into the outbound filter chain. The message is byte-for-byte the same as error 557's wrap ('insert established rule'), so logs alone do not say which chain failed; the chain must be inferred from ordering (it is the second call, after FWD-IN succeeded).

Source

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

		{chainRTFWDOUT, tableFilter},
		{chainRTPRE, tableMangle},
		{chainRTNAT, tableNat},
		{chainRTRDR, tableNat},
		{chainRTMSSCLAMP, tableMangle},
	} {
		// Fallback: clear chains that survived an unclean shutdown.
		if ok, _ := r.iptablesClient.ChainExists(chainInfo.table, chainInfo.chain); ok {
			if err := r.iptablesClient.ClearAndDeleteChain(chainInfo.table, chainInfo.chain); err != nil {
				log.Warnf("clear stale chain %s in %s: %v", chainInfo.chain, chainInfo.table, err)
			}
		}
		if err := r.iptablesClient.NewChain(chainInfo.table, chainInfo.chain); err != nil {
			return fmt.Errorf("create chain %s in table %s: %w", chainInfo.chain, chainInfo.table, err)
		}
	}

	if err := r.insertEstablishedRule(chainRTFWDIN); err != nil {
		return fmt.Errorf("insert established rule: %w", err)
	}

	if err := r.insertEstablishedRule(chainRTFWDOUT); err != nil {
		return fmt.Errorf("insert established rule: %w", err)
	}

	if err := r.addPostroutingRules(); err != nil {
		return fmt.Errorf("add static nat rules: %w", err)
	}

	if err := r.addJumpRules(); err != nil {
		return fmt.Errorf("add jump rules: %w", err)
	}

	if err := r.addMSSClampingRules(); err != nil {
		log.Errorf("failed to add MSS clamping rules: %s", err)
	}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Apply the 557 fixes (module, single instance, lock)
  2. Distinguish the failing chain by position: the error after 'insert established rule' during init is FWD-OUT when FWD-IN already succeeded
  3. Verify both chains: sudo iptables -S NETBIRD-RT-FWD-IN; sudo iptables -S NETBIRD-RT-FWD-OUT
  4. Restart the agent; init recreates and repopulates both chains

Example fix

// before (router_linux.go:463-468): identical messages hide the chain
if err := r.insertEstablishedRule(chainRTFWDIN); err != nil {
	return fmt.Errorf("insert established rule: %w", err)
}
if err := r.insertEstablishedRule(chainRTFWDOUT); err != nil {
	return fmt.Errorf("insert established rule: %w", err)
}
// after
if err := r.insertEstablishedRule(chainRTFWDIN); err != nil {
	return fmt.Errorf("insert established rule into %s: %w", chainRTFWDIN, err)
}
if err := r.insertEstablishedRule(chainRTFWDOUT); err != nil {
	return fmt.Errorf("insert established rule into %s: %w", chainRTFWDOUT, err)
}
Defensive patterns

Strategy: validation

Validate before calling

# verify the second chain explicitly since the error text is ambiguous
sudo iptables -S NETBIRD-RT-FWD-OUT

Try / catch

if err := r.insertEstablishedRule(chainRTFWDOUT); err != nil {
	// include the chain in context when wrapping; message alone is ambiguous
	return fmt.Errorf("insert established rule into %s: %w", chainRTFWDOUT, err)
}

Prevention

When it happens

Trigger: createContainers proceeding past the FWD-IN insert and failing on FWD-OUT: same causes, i.e. missing xt_conntrack, chain removed between creation and insert, or xtables lock contention hitting the second statement.

Common situations: Same environments as 557; intermittent lock contention that only catches the later insert; note the failure leaves FWD-IN populated with no rollback of earlier chains (createContainers returns the error up to init).

Related errors


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