netbirdio/netbird · error

add nat postrouting jump rule: %v

Error message

add nat postrouting jump rule: %v

What it means

First insert in addJumpRules(): puts `-j NETBIRD-RT-NAT` at position 1 of the built-in nat POSTROUTING chain so masqueraded traffic enters NetBird's chain. Wrapped as 'add jump rules' from createContainers(), failure aborts router setup and leaves the custom chains created but unreachable.

Source

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

func (r *router) insertEstablishedRule(chain string) error {
	establishedRule := getConntrackEstablished()

	err := r.iptablesClient.Insert(tableFilter, chain, 1, establishedRule...)
	if err != nil {
		return fmt.Errorf("failed to insert established rule: %v", err)
	}

	ruleKey := "established-" + chain
	r.rules[ruleKey] = establishedRule

	return nil
}

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

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Reproduce manually: `sudo iptables -t nat -I POSTROUTING 1 -j NETBIRD-RT-NAT`
  2. `modprobe iptable_nat` (ip6table_nat for the v6 router)
  3. Verify CAP_NET_ADMIN and that /run/xtables.lock is not held
  4. Check `iptables --version` family consistency (iptables-legacy vs iptables-nft) across the system
  5. Run `netbird down` then `netbird up` for a clean retry

Example fix

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

// after: idempotent insert tolerant of an existing jump from a previous run
exists, _ := r.iptablesClient.Exists(tableNat, chainPOSTROUTING, natRule...)
if !exists {
    if err := r.iptablesClient.Insert(tableNat, chainPOSTROUTING, 1, natRule...); err != nil {
        return fmt.Errorf("add nat postrouting jump rule: %w", err)
    }
}
r.rules[jumpNatPost] = natRule
Defensive patterns

Strategy: validation

Validate before calling

func builtinChainWritable(ipt *iptables.IPTables, table, chain string) error {
    probe := []string{"-m", "comment", "--comment", "nb-probe", "-j", "RETURN"}
    if err := ipt.Insert(table, chain, 1, probe...); err != nil {
        return fmt.Errorf("%s/%s: %w", table, chain, err)
    }
    return ipt.DeleteIfExists(table, chain, probe...)
}

Try / catch

Propagate as fatal for router init; ensure the previously created custom chains get cleaned by the existing cleanup path on the way out.

Prevention

When it happens

Trigger: `iptables -t nat -I POSTROUTING 1 -j NETBIRD-RT-NAT` failing when the nat table is missing (iptable_nat unloaded), CAP_NET_ADMIN is absent, the xtables lock is held, or another manager rewrote POSTROUTING between the agent's reads and writes.

Common situations: Same setup-time class as the other jump-rule errors: module-less kernels, unprivileged containers, docker/firewalld lock contention; also hosts where an admin replaced POSTROUTING wholesale with nft rules while the agent still speaks legacy iptables.

Related errors


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