tailscale/tailscale · error
add table: %w
Error message
add table: %w
What it means
createTableIfNotExist actually tried to create the table (c.AddTable + Flush) and the kernel rejected it. Classic errnos: EPERM (no CAP_NET_ADMIN), EOPNOTSUPP/ENOTSUP (kernel lacks nftables), EEXIST (another writer created the table between the probe and the flush). This is the definitive 'cannot create nftables table' signal.
Source
Thrown at util/linuxfw/nftables_runner.go:421
}
}
return nil, nil
}
// createTableIfNotExist creates a nftables table via connection c if it does
// not exist within the given family.
func createTableIfNotExist(c *nftables.Conn, family nftables.TableFamily, name string) (*nftables.Table, error) {
if t, err := getTableIfExists(c, family, name); err != nil {
return nil, fmt.Errorf("get table: %w", err)
} else if t != nil {
return t, nil
}
t := c.AddTable(&nftables.Table{
Family: family,
Name: name,
})
if err := c.Flush(); err != nil {
return nil, fmt.Errorf("add table: %w", err)
}
return t, nil
}
type errorChainNotFound struct {
chainName string
tableName string
}
func (e errorChainNotFound) Error() string {
return fmt.Sprintf("chain %s not found in table %s", e.chainName, e.tableName)
}
// getChainFromTable returns the chain with the given name from the given table.
// Note that a chain name is unique within a table.
func getChainFromTable(c *nftables.Conn, table *nftables.Table, name string) (*nftables.Chain, error) {
if table == nil {
return nil, fmt.Errorf("could not get chain %q: table not initialized", name)View on GitHub (pinned to 6e0912f979)
Solutions
- Grant CAP_NET_ADMIN (root) to the process.
- Enable nftables in the kernel; verify with 'nft list tables' or 'ls /proc/net/netfilter/nfnetlink_queue' style probes.
- On EEXIST races, re-run the ensure — it is idempotent and the probe will now find the table.
- Fall back to the iptables runner if the kernel permanently lacks nftables.
Example fix
// before
conn := nftables.New()
t, err := createTableIfNotExist(conn, nftables.TableFamilyIPv4, "nat")
// after
if err != nil {
switch {
case errors.Is(err, unix.EPERM):
return fmt.Errorf("need CAP_NET_ADMIN: %w", err)
case errors.Is(err, unix.EOPNOTSUPP):
return fmt.Errorf("kernel lacks nftables support: %w", err)
case errors.Is(err, unix.EEXIST):
t, err = createTableIfNotExist(conn, nftables.TableFamilyIPv4, "nat") // re-probe
}
} Defensive patterns
Strategy: retry
Type guard
func isCreateRace(err error) bool { return errors.Is(err, unix.EEXIST) }
func isUnsupported(err error) bool { return errors.Is(err, unix.EOPNOTSUPP) } Try / catch
t, err := createTableIfNotExist(conn, family, name)
if errors.Is(err, unix.EEXIST) {
t, err = createTableIfNotExist(conn, family, name) // probe now finds it
}
if errors.Is(err, unix.EOPNOTSUPP) {
// switch to iptables runner permanently
} Prevention
- Grant CAP_NET_ADMIN before any table-creating API.
- Treat EEXIST as a benign race: re-probe instead of failing.
- Detect EOPNOTSUPP once at startup and select a supported firewall backend.
When it happens
Trigger: First firewall setup on a host: AddBase, AddDNATRule, EnsureSNATForDst, ClampMSSToPMTU, or svc-chain ensure creating nat/filter tables when the kernel refuses the batch.
Common situations: Missing capabilities in containers (most common); old or stripped kernels without CONFIG_NF_TABLES; racing creators; netlink batch overflow with huge rule sets.
Related errors
- error ensuring nat table: %w
- error ensuring nat table: %w
- error ensuring nat table exists: %w
- error ensuring filter table: %w
- get table: %w
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/463fcbd3367053e6.
Report an issue: GitHub.