netbirdio/netbird · error

failed to insert established rule: %v

Error message

failed to insert established rule: %v

What it means

insertEstablishedRule() puts `-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT` at position 1 of NETBIRD-RT-FWD-IN / NETBIRD-RT-FWD-OUT (filter table) so replies to routed flows are not dropped by later filtering rules. Wrapped by createContainers() as 'insert established rule', its failure aborts router init and routed traffic would break even if setup continued.

Source

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

		"-p", "tcp",
		"--tcp-flags", "SYN,RST", "SYN",
		"-j", "TCPMSS",
		"--set-mss", fmt.Sprintf("%d", mss),
	}
	if err := r.iptablesClient.Append(tableMangle, chainRTMSSCLAMP, ruleOut...); err != nil {
		return fmt.Errorf("add outbound MSS clamp rule: %w", err)
	}
	r.rules["mss-clamp-out"] = ruleOut

	return nil
}

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}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Run the insert by hand to see stderr: `sudo iptables -t filter -I NETBIRD-RT-FWD-IN 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT`
  2. `modprobe xt_conntrack ip_conntrack`
  3. Check `sudo iptables -S | grep NETBIRD-RT-FWD` for chains deleted by other tooling and stop that tooling
  4. Confirm root/CAP_NET_ADMIN for the daemon
  5. Retry `netbird up`; createContainers recreates chains and reruns the insert

Example fix

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

// after: self-heal a vanished chain before inserting
if ok, _ := r.iptablesClient.ChainExists(tableFilter, chain); !ok {
    if err := r.iptablesClient.NewChain(tableFilter, chain); err != nil {
        return fmt.Errorf("recreate chain %s: %w", chain, err)
    }
}
if err := r.iptablesClient.Insert(tableFilter, chain, 1, establishedRule...); err != nil {
    return fmt.Errorf("insert established rule into %s: %w", chain, err)
}
Defensive patterns

Strategy: validation

Validate before calling

func conntrackMatchSupported(ipt *iptables.IPTables) error {
    probe := []string{"-m", "conntrack", "--ctstate", "ESTABLISHED", "-j", "ACCEPT"}
    if err := ipt.Append("filter", "FORWARD", probe...); err != nil {
        return err
    }
    return ipt.DeleteIfExists("filter", "FORWARD", probe...)
}

Try / catch

Return the error from createContainers so routing fails loudly rather than shipping a router that drops reply traffic; no partial continuation.

Prevention

When it happens

Trigger: `iptables -t filter -I NETBIRD-RT-FWD-IN 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT` failing when xt_conntrack is unavailable, the filter table is inaccessible, CAP_NET_ADMIN is missing, or the chain NETBIRD-RT-FWD-IN was deleted by external tooling between chain creation and this insert.

Common situations: Hosts without conntrack match support (rare, custom kernels); 'security' tooling or a second NetBird instance deleting NETBIRD-* chains mid-setup; containers where the filter table is read-only; fresh minimal systems where the conntrack module loads lazily and the first iptables call races module loading.

Related errors


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