tailscale/tailscale · error

EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS is s

Error message

EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS is set but POD_IP is not set

What it means

Thrown by containerboot (the Tailscale container entrypoint) during startup settings validation. EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS turns a Tailscale ingress proxy into a proxy for cluster-internal traffic, which requires knowing the pod's own IP to intercept and forward traffic correctly. That IP is read exclusively from the POD_IP environment variable (cmd/containerboot/settings.go:128), so enabling the feature with POD_IP unset aborts startup before tailscaled launches.

Source

Thrown at cmd/containerboot/settings.go:322

		return errors.New("TS_ID_TOKEN and TS_CLIENT_SECRET cannot both be set")
	}
	if s.IDToken != "" && s.Audience != "" {
		return errors.New("TS_ID_TOKEN and TS_AUDIENCE cannot both be set")
	}
	if s.Audience != "" && s.ClientSecret != "" {
		return errors.New("TS_AUDIENCE and TS_CLIENT_SECRET cannot both be set")
	}
	if s.AuthKey != "" && (s.ClientID != "" || s.ClientSecret != "" || s.IDToken != "" || s.Audience != "") {
		return errors.New("TS_AUTHKEY cannot be used with TS_CLIENT_ID, TS_CLIENT_SECRET, TS_ID_TOKEN, or TS_AUDIENCE.")
	}
	if s.AllowProxyingClusterTrafficViaIngress && s.UserspaceMode {
		return errors.New("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS is not supported in userspace mode")
	}
	if s.AllowProxyingClusterTrafficViaIngress && s.ServeConfigPath == "" {
		return errors.New("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS is set but this is not a cluster ingress proxy")
	}
	if s.AllowProxyingClusterTrafficViaIngress && s.PodIP == "" {
		return errors.New("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS is set but POD_IP is not set")
	}
	if s.EnableForwardingOptimizations && s.UserspaceMode {
		return errors.New("TS_EXPERIMENTAL_ENABLE_FORWARDING_OPTIMIZATIONS is not supported in userspace mode")
	}
	if s.HealthCheckAddrPort != "" {
		log.Printf("[warning] TS_HEALTHCHECK_ADDR_PORT is deprecated and will be removed in 1.82.0. Please use TS_ENABLE_HEALTH_CHECK and optionally TS_LOCAL_ADDR_PORT instead.")
		if _, err := netip.ParseAddrPort(s.HealthCheckAddrPort); err != nil {
			return fmt.Errorf("error parsing TS_HEALTHCHECK_ADDR_PORT value %q: %w", s.HealthCheckAddrPort, err)
		}
	}
	if s.localMetricsEnabled() || s.localHealthEnabled() || s.EgressProxiesCfgPath != "" {
		if _, err := netip.ParseAddrPort(s.LocalAddrPort); err != nil {
			return fmt.Errorf("error parsing TS_LOCAL_ADDR_PORT value %q: %w", s.LocalAddrPort, err)
		}
	}
	if s.DebugAddrPort != "" {
		if _, err := netip.ParseAddrPort(s.DebugAddrPort); err != nil {
			return fmt.Errorf("error parsing TS_DEBUG_ADDR_PORT value %q: %w", s.DebugAddrPort, err)

View on GitHub (pinned to cfe32b8be6)

Solutions

  1. Add a POD_IP env var via the Kubernetes downward API: env: [{name: POD_IP, valueFrom: {fieldRef: {fieldPath: status.podIP}}}]
  2. Check the spelling: it must be POD_IP (singular); POD_IPS is a different variable and does not satisfy this check
  3. If you did not intend to proxy cluster traffic through this ingress proxy, unset EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS
  4. Confirm the pod is scheduled in Kubernetes so the kubelet actually populates status.podIP

Example fix

# before (pod env)
env:
  - name: EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS
    value: 'true'

# after
env:
  - name: EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS
    value: 'true'
  - name: POD_IP
    valueFrom:
      fieldRef:
        fieldPath: status.podIP
Defensive patterns

Strategy: validation

Validate before calling

#!/usr/bin/env bash
# Fail fast before container start when proxying cluster traffic is enabled.
set -euo pipefail
if [[ "${EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS:-}" == "true" ]]; then
  if [[ -z "${POD_IP:-}" ]]; then
    echo 'POD_IP must be set (downward API: status.podIP) when EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS=true' >&2
    exit 1
  fi
fi

Prevention

When it happens

Trigger: Container starts with EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS set to a truthy value while POD_IP is unset/empty. Typical causes: the downward-API env block was omitted from the pod spec, the variable was misspelled (POD_IP vs POD_IPS, the latter being a separate multi-IP setting), or the manifest was copied from a Kubernetes deployment into docker run where no downward API exists.

Common situations: Porting an ingress-proxy StatefulSet between charts and dropping the fieldRef block; renaming env vars during upgrades; attempting to run the experimental cluster-traffic proxying feature outside Kubernetes where no kubelet populates status.podIP.

Related errors


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