juanfont/headscale · error
errTrustedProxyZeroRange
errTrustedProxyZeroRange
Error message
trusted_proxies[%d] %q: 0.0.0.0/0 and ::/0 are not allowed
What it means
Returned when a trusted_proxies entry is 0.0.0.0/0 or ::/0 (prefix length 0). Trusting every possible source defeats the peer-trust gate that validates X-Forwarded-For/X-Real-IP headers, so headscale rejects it as an almost-certain misconfiguration rather than silently trusting spoofable client IPs.
Source
Thrown at hscontrol/types/config.go:1080
}
// trustedProxies rejects 0.0.0.0/0 and ::/0 because they defeat the
// peer-trust gate and almost always indicate misconfiguration.
func trustedProxies() ([]netip.Prefix, error) {
raw := viper.GetStringSlice("trusted_proxies")
if len(raw) == 0 {
return nil, nil
}
out := make([]netip.Prefix, 0, len(raw))
for i, s := range raw {
p, err := netip.ParsePrefix(s)
if err != nil {
return nil, fmt.Errorf("trusted_proxies[%d] %q: %w", i, s, err)
}
if p.Bits() == 0 {
return nil, fmt.Errorf("trusted_proxies[%d] %q: %w", i, s, errTrustedProxyZeroRange)
}
out = append(out, p.Masked())
}
return out, nil
}
// LoadCLIConfig returns the needed configuration for the CLI client
// of Headscale to connect to a Headscale server.
func LoadCLIConfig() (*Config, error) {
logConfig := logConfig()
zerolog.SetGlobalLevel(logConfig.Level)
return &Config{
DisableUpdateCheck: viper.GetBool("disable_check_updates"),
UnixSocket: viper.GetString("unix_socket"),
CLI: CLIConfig{View on GitHub (pinned to 565fd254d0)
Solutions
- Restrict trusted_proxies to the actual CIDR(s) your reverse proxy lives in, e.g. 172.16.0.0/12
- If the proxy address is dynamic, put it in a dedicated Docker/Kubernetes network and trust that subnet
- Remove trusted_proxies entirely and pass the real client IP out-of-band if no fixed proxy subnet exists
Example fix
# before trusted_proxies: - 0.0.0.0/0 # after trusted_proxies: - 172.16.0.0/12
Defensive patterns
Strategy: validation
Validate before calling
import "net/netip"
func assertNoZeroRangeProxies(raw []string) error {
for i, s := range raw {
p, err := netip.ParsePrefix(s)
if err != nil {
continue // parse error handled separately
}
if p.Bits() == 0 {
return fmt.Errorf("entry %d (%q) is an all-ips range; refuse to trust everyone", i, s)
}
}
return nil
} Prevention
- Never carry allow-all CIDRs (0.0.0.0/0, ::/0) into trusted_proxies
- Document the proxy network CIDR next to the config key so operators copy the right value
- Add a config lint rule that fails CI on any /0 entry in trusted_proxies
When it happens
Trigger: Configuring trusted_proxies: ["0.0.0.0/0"] or ["::/0"] and loading the server configuration. Detected via p.Bits() == 0 after successful prefix parsing.
Common situations: Operators copy a permissive allow-all CIDR from other proxy configs (nginx set_real_ip_from style) or try to 'make it work' when their proxy's address keeps changing. It can enable IP spoofing against ACLs or rate limits keyed on client IP.
Related errors
- trusted_proxies[%d] %q: %w
- STUN address not set
- initial DERPMap is empty, Headscale requires at least one en
- database type not supported
- unsupported policy mode
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/62b7d3f5044b7cc2.
Report an issue: GitHub.