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
- Add a POD_IP env var via the Kubernetes downward API: env: [{name: POD_IP, valueFrom: {fieldRef: {fieldPath: status.podIP}}}]
- Check the spelling: it must be POD_IP (singular); POD_IPS is a different variable and does not satisfy this check
- If you did not intend to proxy cluster traffic through this ingress proxy, unset EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS
- 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
- Keep the POD_IP downward-API env block adjacent to the feature flag in the same manifest so they are removed or copied together
- Add a pod-spec lint (conftest/kubeconform custom rule) that rejects manifests enabling the feature without a POD_IP fieldRef
- Use kubectl describe pod to confirm POD_IP is actually populated before rolling the flag out fleet-wide
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
- TS_INGRESS_PROXIES_CONFIG_PATH is only supported for Tailsca
- unexpected egress service config- neither tailnet target IP
- TS_EXPERIMENTAL_DEST_DNS_NAME and TS_DEST_IP cannot both be
- Both TS_TAILNET_TARGET_IP and TS_TAILNET_FQDN cannot be set
- EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS is s
AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15).
Data as JSON: /api/errors/0abafca31b0664c3.
Report an issue: GitHub.