tailscale/tailscale · error

create forward chain: %w

Error message

create forward chain: %w

What it means

AddChains failed to create the ts-forward regular chain inside the filter table (createChainIfNotExist). It fires when the chain-creation netlink message fails — often because the table state changed concurrently or the operation was rejected by the kernel.

Source

Thrown at util/linuxfw/nftables_runner.go:945

}

// AddChains creates custom Tailscale chains in netfilter via nftables
// if the ts-chain doesn't already exist.
func (n *nftablesRunner) AddChains() error {
	polAccept := nftables.ChainPolicyAccept
	for _, table := range n.getTables() {
		// Create the filter table if it doesn't exist, this table name is the same
		// as the name used by iptables-nft and ufw. We install rules into the
		// same conventional table so that `accept` verdicts from our jump
		// chains are conclusive.
		filter, err := createTableIfNotExist(n.conn, table.Proto, "filter")
		if err != nil {
			return fmt.Errorf("create table: %w", err)
		}
		table.Filter = filter
		// Adding the "conventional chains" that are used by iptables-nft and ufw.
		if err = createChainIfNotExist(n.conn, chainInfo{filter, "FORWARD", nftables.ChainTypeFilter, nftables.ChainHookForward, nftables.ChainPriorityFilter, &polAccept}); err != nil {
			return fmt.Errorf("create forward chain: %w", err)
		}
		if err = createChainIfNotExist(n.conn, chainInfo{filter, "INPUT", nftables.ChainTypeFilter, nftables.ChainHookInput, nftables.ChainPriorityFilter, &polAccept}); err != nil {
			return fmt.Errorf("create input chain: %w", err)
		}
		// Adding the tailscale chains that contain our rules.
		if err = createChainIfNotExist(n.conn, chainInfo{filter, chainNameForward, chainTypeRegular, nil, nil, nil}); err != nil {
			return fmt.Errorf("create forward chain: %w", err)
		}
		if err = createChainIfNotExist(n.conn, chainInfo{filter, chainNameInput, chainTypeRegular, nil, nil, nil}); err != nil {
			return fmt.Errorf("create input chain: %w", err)
		}

		// Create the nat table if it doesn't exist, this table name is the same
		// as the name used by iptables-nft and ufw. We install rules into the
		// same conventional table so that `accept` verdicts from our jump
		// chains are conclusive.
		nat, err := createTableIfNotExist(n.conn, table.Proto, "nat")
		if err != nil {

View on GitHub (pinned to 6e0912f979)

Solutions

  1. Retry AddChains; concurrent table mutations cause transient failures.
  2. Verify the filter table exists and inspect 'nft list ruleset'.
  3. Check capabilities and nftables backend availability.
Defensive patterns

Strategy: try-catch

When it happens

Trigger: Thrown at util/linuxfw/nftables_runner.go:945 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18). Data as JSON: /api/errors/fb809be039ef93a5. Report an issue: GitHub.