netbirdio/netbird · error
add static nat rules: %w
Error message
add static nat rules: %w
What it means
Returned by the NetBird iptables router manager when addPostroutingRules() fails while programming the NETBIRD-RT-NAT chain in the nat table. The agent (running as root) shells out to iptables via coreos/go-iptables to append two static masquerade rules, and any non-zero iptables exit is wrapped with this message inside createContainers(). It aborts router initialization, so network routes / exit-node forwarding cannot come up.
Source
Thrown at client/firewall/iptables/router_linux.go:471
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)
}
return nil
}
// setupDataPlaneMark configures the fwmark for the data plane
func (r *router) setupDataPlaneMark() error {
var merr *multierror.Error
preRule := []string{
"-i", r.wgIface.Name(),View on GitHub (pinned to 93e97f4bf1)
Solutions
- Reproduce by hand as root: `iptables -t nat -A NETBIRD-RT-NAT -m mark --mark 0x1000 ! -o lo -j MASQUERADE` and read the stderr in the error chain
- Load the missing modules: `modprobe iptable_nat xt_MASQUERADE xt_mark` (and ip6table_nat for IPv6)
- Install an iptables implementation: `apt install iptables` (or iptables-nft) and confirm `iptables --version` works for both families
- Run the agent as root or grant CAP_NET_ADMIN to the container; verify with `iptables -L -n`
- Free the xtables lock: stop competing firewall tooling or wait and retry `netbird up`
Example fix
// before: agent fails at startup with only the wrapped error
if err := r.addPostroutingRules(); err != nil {
return fmt.Errorf("add static nat rules: %w", err)
}
// after: pre-flight the nat table so the real cause surfaces early
if err := probeNatTable(r.iptablesClient); err != nil {
return fmt.Errorf("nat table check: %w", err)
}
if err := r.addPostroutingRules(); err != nil {
return fmt.Errorf("add static nat rules: %w", err)
} Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight before starting the router: prove the nat table accepts rules
func probeNatTable(ipt *iptables.IPTables) error {
probe := []string{"-m", "comment", "--comment", "nb-nat-probe", "-j", "RETURN"}
if err := ipt.Append("nat", "POSTROUTING", probe...); err != nil {
return fmt.Errorf("nat table unusable: %w", err)
}
return ipt.DeleteIfExists("nat", "POSTROUTING", probe...)
} Type guard
func isPermissionError(err error) bool {
var ee *exec.ExitError
return errors.As(err, &ee) && strings.Contains(err.Error(), "Permission denied")
} Try / catch
Abort setup on first postrouting failure and run the router Reset path so half-created chains are removed before returning the error upward.
Prevention
- Load iptable_nat, xt_MASQUERADE and xt_mark before the agent starts (systemd ExecStartPre=/usr/sbin/modprobe ...)
- Smoke-test `iptables -t nat -L -n` in the container image build so missing binaries surface at build time
- Run the agent as root or with CAP_NET_ADMIN; never partial privileges
- Keep host firewall tooling from batching iptables writes while netbird starts
When it happens
Trigger: Calling router.init() -> createContainers() -> addPostroutingRules() when `iptables -t nat -A NETBIRD-RT-NAT -m mark --mark 0x... ! -o lo -j MASQUERADE` fails: nat table missing (iptable_nat/nft module not loaded), MASQUERADE or mark match (xt_mark) unavailable, missing CAP_NET_ADMIN, no iptables binary, or /run/xtables.lock held by another process.
Common situations: Containers (Docker/LXC/OpenVZ) without CAP_NET_ADMIN or without iptables kernel modules; minimal Debian/Alpine images or nftables-only distros without iptables-nft installed; kernels shipped by some VPS providers without iptable_nat; concurrent firewall daemons (firewalld, ufw, docker) holding the xtables lock during `netbird up`.
Related errors
- add nat rule: %w
- add inverse nat rule: %w
- remove nat rule: %w
- remove inverse nat rule: %w
- add jump rules: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/c4cb1921822964ad.
Report an issue: GitHub.