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
- Reproduce manually: `sudo iptables -t nat -I PREROUTING 1 -j NETBIRD-RT-RDR`
- `modprobe iptable_nat` / `modprobe ip6table_nat`
- Check that NETBIRD-RT-RDR exists in `iptables -t nat -S` and recreate with -N if a cleanup half-finished
- Verify daemon runs as root and no other process holds /run/xtables.lock
- 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
- modprobe iptable_nat for both address families
- Prevent external deletion of NETBIRD-RT-RDR during operation
- Run netbird down before host-level nat table surgery
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
- add static nat rules: %w
- add outbound masquerade rule: %v
- add return masquerade rule: %v
- add nat postrouting jump rule: %v
- add nat rule: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/0c3bb8f211fcdbf0.
Report an issue: GitHub.