caddyserver/caddy · error
invalid IP address: '%s': %v
Error message
invalid IP address: '%s': %v
What it means
Thrown while provisioning the remote_ip/client_ip matcher when an entry has no '/', so it is parsed as a bare IP with netip.ParseAddr, which rejects it. Note that anything after '%' is stripped first (zone identifier handling), so the reported string may differ slightly from what you wrote. Config load fails and the old config (if any) keeps running.
Source
Thrown at modules/caddyhttp/ip_matchers.go:308
// 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 port
}
// Some IPv6-Addresses can contain zone identifiers at the end,
// which are separated with "%"
zoneID := ""
if strings.Contains(ipStr, "%") {View on GitHub (pinned to 50e54ee279)
Solutions
- Replace the value with a valid literal IP: remote_ip 192.168.1.10.
- If you need to match a hostname, use the `host` matcher instead.
- If matching a whole subnet, add the mask so it goes down the CIDR path: 10.0.0.0/8.
- Sanitize generated configs (trim spaces, drop empty list entries) before feeding them to Caddy.
Example fix
// before @lan remote_ip example.com // after @lan remote_ip 192.168.1.10
Defensive patterns
Strategy: validation
Validate before calling
import "net/netip"
func validBareIPs(entries []string) bool {
for _, e := range entries {
if strings.Contains(e, "/") {
continue
}
if _, err := netip.ParseAddr(e); err != nil {
return false
}
}
return true
} Prevention
- Never put hostnames in remote_ip/client_ip; use the host matcher.
- Trim whitespace and drop empty tokens when templating matcher lists.
- Avoid leading-zero octets; netip rejects them.
When it happens
Trigger: remote_ip set to a hostname (e.g. `remote_ip example.com`), a malformed address like `192.168.1.256` or `fe80:::1`, an empty string, or a value that became empty/invalid after the '%' zone split (`fe80::1%eth0` is fine, `%-bad` is not).
Common situations: Using hostnames or placeholders that expand to text instead of literals in IP matchers; trailing whitespace/comma artifacts from templated configs; expecting IPv4-mapped form `::ffff:192.168.1.1` to be normalized against an IPv4 list (it is not, by design).
Related errors
- invalid IP address: '%s': %v
- parsing listener address: %v
- must be exactly one listener address; cannot listen on: %s
- indexing config: %v
- %s: %s field must be a string or number
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/e5b1a5ccc95ac547.
Report an issue: GitHub.