tailscale/tailscale · error

TS_EXPERIMENTAL_DEST_DNS_NAME and TS_DEST_IP cannot both be

Error message

TS_EXPERIMENTAL_DEST_DNS_NAME and TS_DEST_IP cannot both be set

What it means

containerboot settings validation rejects setting both TS_EXPERIMENTAL_DEST_DNS_NAME and TS_DEST_IP. The proxy destination must be specified exactly one way (by IP or by DNS name); two conflicting targets would make forwarding behavior ambiguous, so startup aborts.

Source

Thrown at cmd/containerboot/settings.go:263

		dir, file := path.Split(s.TailscaledConfigFilePath)
		if _, err := os.Stat(dir); err != nil {
			return fmt.Errorf("error validating whether directory with tailscaled config file %s exists: %w", dir, err)
		}
		if _, err := os.Stat(s.TailscaledConfigFilePath); err != nil {
			return fmt.Errorf("error validating whether tailscaled config directory %q contains tailscaled config for current capability version %q: %w. If this is a Tailscale Kubernetes operator proxy, please ensure that the version of the operator is not older than the version of the proxy", dir, file, err)
		}
		if _, err := conffile.Load(s.TailscaledConfigFilePath); err != nil {
			return fmt.Errorf("error validating tailscaled configfile contents: %w", err)
		}
	}
	if s.ProxyTargetIP != "" && s.UserspaceMode {
		return errors.New("TS_DEST_IP is not supported with TS_USERSPACE")
	}
	if s.ProxyTargetDNSName != "" && s.UserspaceMode {
		return errors.New("TS_EXPERIMENTAL_DEST_DNS_NAME is not supported with TS_USERSPACE")
	}
	if s.ProxyTargetDNSName != "" && s.ProxyTargetIP != "" {
		return errors.New("TS_EXPERIMENTAL_DEST_DNS_NAME and TS_DEST_IP cannot both be set")
	}
	if s.TailnetTargetIP != "" && s.UserspaceMode {
		return errors.New("TS_TAILNET_TARGET_IP is not supported with TS_USERSPACE")
	}
	if s.TailnetTargetFQDN != "" && s.UserspaceMode {
		return errors.New("TS_TAILNET_TARGET_FQDN is not supported with TS_USERSPACE")
	}
	if s.TailnetTargetFQDN != "" && s.TailnetTargetIP != "" {
		return errors.New("Both TS_TAILNET_TARGET_IP and TS_TAILNET_FQDN cannot be set")
	}
	if s.TailscaledConfigFilePath != "" &&
		(s.AcceptDNS != nil ||
			s.AuthKey != "" ||
			s.Routes != nil ||
			s.ExtraArgs != "" ||
			s.Hostname != "" ||
			s.ClientID != "" ||
			s.ClientSecret != "" ||

View on GitHub (pinned to cfe32b8be6)

Solutions

  1. Decide on one destination mode: keep TS_DEST_IP for a stable IP, or TS_EXPERIMENTAL_DEST_DNS_NAME for a DNS-resolved backend, and delete the other
  2. If the DNS name and IP refer to the same backend, prefer the DNS name for stability across pod restarts
  3. Apply the corrected spec

Example fix

 # before
env:
  - name: TS_DEST_IP
    value: "10.24.0.5"
  - name: TS_EXPERIMENTAL_DEST_DNS_NAME
    value: "backend.default.svc.cluster.local"

 # after
env:
  - name: TS_EXPERIMENTAL_DEST_DNS_NAME
    value: "backend.default.svc.cluster.local"
  # TS_DEST_IP removed
Defensive patterns

Strategy: validation

Validate before calling

if [ -n "$TS_DEST_IP" ] && [ -n "$TS_EXPERIMENTAL_DEST_DNS_NAME" ]; then
  echo "invalid config: set only one of TS_DEST_IP or TS_EXPERIMENTAL_DEST_DNS_NAME" >&2
  exit 1
fi

Prevention

When it happens

Trigger: A proxy Deployment with both TS_DEST_IP and TS_EXPERIMENTAL_DEST_DNS_NAME set; migrating a manifest from IP-based to DNS-name-based destination without removing the old variable; Helm values that render both keys.

Common situations: Copy-paste during migration between destination modes; charts that template both env vars and enable both when values are populated.

Related errors


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