caddyserver/caddy · error
parsing CIDR expression: '%s': %v
Error message
parsing CIDR expression: '%s': %v
What it means
Each entry in trusted_proxies is parsed at provision time: strings containing '/' go through netip.ParsePrefix (CIDR), everything else through netip.ParseAddr. A malformed CIDR (bad bits field, stray spaces, embedded hostname) or an invalid bare IP yields 'parsing CIDR expression: '%s': %v' or 'invalid IP address'. Note there is no hostname resolution — the value must already be an IP or CIDR literal.
Source
Thrown at modules/caddyhttp/reverseproxy/reverseproxy.go:324
if err != nil {
return fmt.Errorf("loading circuit breaker: %s", err)
}
h.CB = mod.(CircuitBreaker)
}
if h.DynamicUpstreamsRaw != nil {
mod, err := ctx.LoadModule(h, "DynamicUpstreamsRaw")
if err != nil {
return fmt.Errorf("loading upstream source module: %v", err)
}
h.DynamicUpstreams = mod.(UpstreamSource)
}
// parse trusted proxy CIDRs ahead of time
for _, str := range h.TrustedProxies {
if strings.Contains(str, "/") {
ipNet, err := netip.ParsePrefix(str)
if err != nil {
return fmt.Errorf("parsing CIDR expression: '%s': %v", str, err)
}
h.trustedProxies = append(h.trustedProxies, ipNet)
} else {
ipAddr, err := netip.ParseAddr(str)
if err != nil {
return fmt.Errorf("invalid IP address: '%s': %v", str, err)
}
ipNew := netip.PrefixFrom(ipAddr, ipAddr.BitLen())
h.trustedProxies = append(h.trustedProxies, ipNew)
}
}
// ensure any embedded headers handler module gets provisioned
// (see https://caddy.community/t/set-cookie-manipulation-in-reverse-proxy/7666?u=matt
// for what happens if we forget to provision it)
if h.Headers != nil {
err := h.Headers.Provision(ctx)
if err != nil {View on GitHub (pinned to 50e54ee279)
Solutions
- Replace hostnames with concrete IPs or CIDRs (resolve once, or maintain the list manually): trusted_proxies 10.0.0.0/8 192.168.0.0/16.
- For CIDRs, mask host bits and keep the prefix length valid (0–32 for v4, 0–128 for v6): use 10.0.0.0/24 not 10.0.0.1/24.
- If the list comes from an env placeholder, print the rendered config (caddy adapt) and sanitize the injected value.
- Use private_ranges only when full RFC1918+loopback trust is acceptable.
Example fix
# before
reverse_proxy localhost:8080 {
trusted_proxies proxy.internal 10.0.0.1/24
}
# after
reverse_proxy localhost:8080 {
trusted_proxies 10.0.0.0/24 192.168.1.10
} Defensive patterns
Strategy: validation
Validate before calling
import "net/netip"
func validateTrustedProxies(entries []string) error {
for _, s := range entries {
if strings.Contains(s, "/") {
if _, err := netip.ParsePrefix(s); err != nil {
return fmt.Errorf("bad CIDR %q: %w", s, err)
}
continue
}
if _, err := netip.ParseAddr(s); err != nil {
return fmt.Errorf("bad IP %q: %w", s, err)
}
}
return nil
} Prevention
- Only IPs and CIDR literals are allowed — resolve hostnames before writing them into config.
- Use masked CIDRs (10.0.0.0/24), never host-bit forms (10.0.0.1/24).
- When injecting trusted_proxies from env vars, sanitize and caddy adapt the rendered config in CI.
When it happens
Trigger: trusted_proxies 10.0.0.0/8 192.168.1.5 works; trusted_proxies proxy.internal (a hostname), 10.0.0.0/33 (invalid prefix length), 10.0.0.1/24 (host bits set — ParsePrefix rejects), or a value with whitespace/quotes from env-var interpolation fails.
Common situations: See trigger scenarios.
Related errors
- unsupported network type: %s
- invalid IP address: '%s': %v
- if HTTP/3 is enabled to the upstream, no other HTTP versions
- client_certificate_file specified without client_certificate
- client_certificate_key_file specified without client_certifi
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/5cef1d1a6f0f7a64.
Report an issue: GitHub.