tailscale/tailscale · error

error syncing egress service configs: %w

Error message

error syncing egress service configs: %w

What it means

Middle wrapper in egressProxy.sync around syncEgressConfigs, the function that computes and applies the netfilter diff (delete removed services, resolve targets, add/update rules, SNAT, MSS clamping). Any failure inside that pipeline — wrapped %w — surfaces here, with the specific stage named by an inner wrapper such as 'error setting up SNAT rule' or 'error adding rules'.

Source

Thrown at cmd/containerboot/egressservices.go:182

	ep.longSleep = sleepDuration * 10
}

// sync triggers an egress proxy config resync. The resync calculates the diff between config and status to determine if
// any firewall rules need to be updated. Currently using status in state Secret as a reference for what is the current
// firewall configuration is good enough because - the status is keyed by the Pod IP - we crash the Pod on errors such
// as failed firewall update
func (ep *egressProxy) sync(ctx context.Context, nm netmapState) error {
	cfgs, err := ep.getConfigs()
	if err != nil {
		return fmt.Errorf("error retrieving egress service configs: %w", err)
	}
	status, err := ep.getStatus(ctx)
	if err != nil {
		return fmt.Errorf("error retrieving current egress proxy status: %w", err)
	}
	newStatus, err := ep.syncEgressConfigs(cfgs, status, nm)
	if err != nil {
		return fmt.Errorf("error syncing egress service configs: %w", err)
	}
	if !servicesStatusIsEqual(newStatus, status) {
		if err := ep.setStatus(ctx, newStatus, nm); err != nil {
			return fmt.Errorf("error setting egress proxy status: %w", err)
		}
	}
	return nil
}

// addrsHaveChanged returns true if the provided netmap state contains tailnet address change for this proxy node.
func (ep *egressProxy) addrsHaveChanged(nm netmapState) bool {
	return !views.SliceEqual(views.SliceOf(ep.tailnetAddrs), nm.self.Addresses())
}

// syncEgressConfigs adds and deletes firewall rules to match the desired
// configuration. It uses the provided status to determine what is currently
// applied and updates the status after a successful sync.
func (ep *egressProxy) syncEgressConfigs(cfgs egressservices.Configs, status *egressservices.Status, nm netmapState) (*egressservices.Status, error) {

View on GitHub (pinned to cfe32b8be6)

Solutions

  1. Look at the innermost wrapped message — it names the exact netfilter stage that failed
  2. Ensure the proxy container grants NET_ADMIN (and runs with adequate privileges for iptables/nft)
  3. Check for iptables/xtables lock contention on the node (other agents, kube-proxy) and retry after they settle
  4. Align the containerboot firewall mode (iptables vs nftables) with what the host kernel supports
Defensive patterns

Strategy: try-catch

Try / catch

newStatus, err := ep.syncEgressConfigs(cfgs, status, nm)
if err != nil {
	// match on the inner stage wrappers to classify
	msg := err.Error()
	switch {
	case strings.Contains(msg, "SNAT"), strings.Contains(msg, "adding rules"):
		// netfilter/capability problem — check NET_ADMIN
	case strings.Contains(msg, "tailnet target"):
		// config problem — fix annotation
	}
	return fmt.Errorf("error syncing egress service configs: %w", err)
}

Prevention

When it happens

Trigger: netfilter operations failing: EnsureSNATForDst or ClampMSSToPMTU denied (missing NET_ADMIN), ensureRulesAdded/ensureRulesDeleted failing on iptables/nft errors (table lock, backend mismatch), or target resolution errors from tailnetTargetIPsForSvc.

Common situations: Container securityContext missing capabilities: ["NET_ADMIN"]; iptables lock contention with kube-proxy or other DaemonSets on a busy node; host firewall switched to a backend the chosen firewall mode does not support.

Related errors


AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15). Data as JSON: /api/errors/bffb0e6442fe6ca3. Report an issue: GitHub.