caddyserver/caddy · error
parsing CIDR expression '%s': %v
Error message
parsing CIDR expression '%s': %v
What it means
Thrown while provisioning the remote_ip/client_ip matcher when an entry contains '/' and is therefore treated as a CIDR, but netip.ParsePrefix rejects it. The string is passed through verbatim in the message, so the offending entry is visible. This is a config-load-time error: Caddy refuses to start/apply the config containing the bad matcher.
Source
Thrown at modules/caddyhttp/ip_matchers.go:302
func provisionCidrsZonesFromRanges(ranges []string) ([]*netip.Prefix, []string, error) {
cidrs := []*netip.Prefix{}
zones := []string{}
repl := caddy.NewReplacer()
for _, str := range ranges {
str = repl.ReplaceAll(str, "")
// Exclude the zone_id from the IP
if strings.Contains(str, "%") {
split := strings.Split(str, "%")
str = split[0]
// write zone identifiers in m.zones for matching later
zones = append(zones, split[1])
} else {
zones = append(zones, "")
}
if strings.Contains(str, "/") {
ipNet, err := netip.ParsePrefix(str)
if err != nil {
return nil, nil, fmt.Errorf("parsing CIDR expression '%s': %v", str, err)
}
cidrs = append(cidrs, &ipNet)
} else {
ipAddr, err := netip.ParseAddr(str)
if err != nil {
return nil, nil, fmt.Errorf("invalid IP address: '%s': %v", str, err)
}
ipNew := netip.PrefixFrom(ipAddr, ipAddr.BitLen())
cidrs = append(cidrs, &ipNew)
}
}
return cidrs, zones, nil
}
func parseIPZoneFromString(address string) (netip.Addr, string, error) {
ipStr, _, err := net.SplitHostPort(address)
if err != nil {
ipStr = address // OK; probably didn't have a portView on GitHub (pinned to 50e54ee279)
Solutions
- Correct the CIDR to a valid prefix: IPv4 masks 0-32, IPv6 masks 0-128 (e.g. 192.168.1.0/24).
- If you meant a single host, drop the slash: remote_ip 10.0.0.1.
- Verify entries before deploying with a quick Go check: netip.ParsePrefix("192.168.1.0/24").
- Run `caddy adapt --config Caddyfile --adapter caddyfile` then `caddy validate` to catch this before reload.
Example fix
// before (Caddyfile) @internal remote_ip 192.168.1.0/33 // after @internal remote_ip 192.168.1.0/24
Defensive patterns
Strategy: validation
Validate before calling
// Go: pre-validate every CIDR entry before building the config
import "net/netip"
func validCIDRs(entries []string) bool {
for _, e := range entries {
if !strings.Contains(e, "/") {
continue
}
if _, err := netip.ParsePrefix(e); err != nil {
return false
}
}
return true
} Prevention
- Run `caddy validate --config <file>` in CI before deploy.
- Generate IP lists from typed sources (netip.Prefix values), not free-form strings.
- Remember bounds: IPv4 /0-/32, IPv6 /0-/128.
When it happens
Trigger: Configuring `remote_ip 192.168.1.0/33` (mask too long), `10.0.0.0/8/24`, an IPv6 prefix like `fe80::/10x`, or a bare IP with a trailing slash `10.0.0.1/`. Also triggered when a zone identifier was stripped (everything after '%') leaving a malformed prefix.
Common situations: Typos in subnet masks in a Caddyfile or JSON config; assuming an arbitrary bit count is valid (IPv4 max /32, IPv6 max /128); copy-pasting routes that include both an IP and mask separated incorrectly; IPv4-mapped IPv6 notation that netip refuses in prefixes.
Related errors
- invalid IP address: '%s': %v
- parsing CIDR expression: '%s': %v
- unknown object ID '%s'
- decoding request body: %w, at offset %d
- decoding request body: %w
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/449b5e046cef6a3f.
Report an issue: GitHub.