caddyserver/caddy · error
invalid IP address
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.
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
- automation policy is the second policy that acts as…
- cache capacity must be >= 0
- destinations != defaults
- destination must be a placeholder and only a placeholder
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/e5b1a5ccc95ac547.
Report an issue: GitHub.
Appendix: 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)