netbirdio/netbird · error

add nat prerouting jump rule: %v

Error message

add nat prerouting jump rule: %v

What it means

Third insert in addJumpRules(): `-j NETBIRD-RT-RDR` at position 1 of nat PREROUTING, directing inbound traffic into NetBird's redirect/DNAT chain. As with the sibling jump rules, the error propagates as 'add jump rules' and aborts router container setup, and the two jump rules inserted before it may remain in place until the next cleanup.

Source

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

func (r *router) addJumpRules() error {
	// Jump to nat chain
	natRule := []string{"-j", chainRTNAT}
	if err := r.iptablesClient.Insert(tableNat, chainPOSTROUTING, 1, natRule...); err != nil {
		return fmt.Errorf("add nat postrouting jump rule: %v", err)
	}
	r.rules[jumpNatPost] = natRule

	// Jump to mangle prerouting chain
	preRule := []string{"-j", chainRTPRE}
	if err := r.iptablesClient.Insert(tableMangle, chainPREROUTING, 1, preRule...); err != nil {
		return fmt.Errorf("add mangle prerouting jump rule: %v", err)
	}
	r.rules[jumpManglePre] = preRule

	// Jump to nat prerouting chain
	rdrRule := []string{"-j", chainRTRDR}
	if err := r.iptablesClient.Insert(tableNat, chainPREROUTING, 1, rdrRule...); err != nil {
		return fmt.Errorf("add nat prerouting jump rule: %v", err)
	}
	r.rules[jumpNatPre] = rdrRule

	return nil
}

func (r *router) cleanJumpRules() error {
	for _, ruleKey := range []string{jumpNatPost, jumpManglePre, jumpNatPre, jumpMSSClamp} {
		if rule, exists := r.rules[ruleKey]; exists {
			var table, chain string
			switch ruleKey {
			case jumpNatPost:
				table = tableNat
				chain = chainPOSTROUTING
			case jumpManglePre:
				table = tableMangle
				chain = chainPREROUTING
			case jumpNatPre:

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Reproduce manually: `sudo iptables -t nat -I PREROUTING 1 -j NETBIRD-RT-RDR`
  2. `modprobe iptable_nat` / `modprobe ip6table_nat`
  3. Check that NETBIRD-RT-RDR exists in `iptables -t nat -S` and recreate with -N if a cleanup half-finished
  4. Verify daemon runs as root and no other process holds /run/xtables.lock
  5. Run `netbird down` to remove the partially applied sibling jumps, then `netbird up`

Example fix

// before
if err := r.iptablesClient.Insert(tableNat, chainPREROUTING, 1, rdrRule...); err != nil {
    return fmt.Errorf("add nat prerouting jump rule: %v", err)
}

// after: clean up earlier jumps on failure so setup stays atomic
if err := r.iptablesClient.Insert(tableNat, chainPREROUTING, 1, rdrRule...); err != nil {
    if ok, _ := r.iptablesClient.ChainExists(tableNat, chainPOSTROUTING); ok {
        _ = r.iptablesClient.DeleteIfExists(tableNat, chainPOSTROUTING, natRule...)
        delete(r.rules, jumpNatPost)
    }
    return fmt.Errorf("add nat prerouting jump rule: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

func rdrChainExists(ipt *iptables.IPTables) bool {
    ok, err := ipt.ChainExists("nat", "NETBIRD-RT-RDR")
    return err == nil && ok
}

Try / catch

Catch at the createContainers boundary and roll back the two earlier jump-rule inserts before returning, preserving the built-in chains' original state.

Prevention

When it happens

Trigger: `iptables -t nat -I PREROUTING 1 -j NETBIRD-RT-RDR` failing on missing iptable_nat, missing CAP_NET_ADMIN, held xtables lock, or external deletion of the NETBIRD-RT-RDR chain between its creation and this insert.

Common situations: Same family as the other two jump-rule errors: stripped kernels, containers, lock contention, legacy/nft mismatch. Frequently the v6 instance fails first on hosts where ip6table_nat is not loaded even though IPv4 NAT works.

Related errors


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