{"record":{"id":"e5b1a5ccc95ac547","repo":"caddyserver/caddy","slug":"invalid-ip-address-s-v","errorCode":null,"errorMessage":"invalid IP address: '%s': %v","messagePattern":"invalid IP address: '(.+?)': (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddyhttp/ip_matchers.go","lineNumber":308,"sourceCode":"\t\t// Exclude the zone_id from the IP\n\t\tif strings.Contains(str, \"%\") {\n\t\t\tsplit := strings.Split(str, \"%\")\n\t\t\tstr = split[0]\n\t\t\t// write zone identifiers in m.zones for matching later\n\t\t\tzones = append(zones, split[1])\n\t\t} else {\n\t\t\tzones = append(zones, \"\")\n\t\t}\n\t\tif strings.Contains(str, \"/\") {\n\t\t\tipNet, err := netip.ParsePrefix(str)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, fmt.Errorf(\"parsing CIDR expression '%s': %v\", str, err)\n\t\t\t}\n\t\t\tcidrs = append(cidrs, &ipNet)\n\t\t} else {\n\t\t\tipAddr, err := netip.ParseAddr(str)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, fmt.Errorf(\"invalid IP address: '%s': %v\", str, err)\n\t\t\t}\n\t\t\tipNew := netip.PrefixFrom(ipAddr, ipAddr.BitLen())\n\t\t\tcidrs = append(cidrs, &ipNew)\n\t\t}\n\t}\n\treturn cidrs, zones, nil\n}\n\nfunc parseIPZoneFromString(address string) (netip.Addr, string, error) {\n\tipStr, _, err := net.SplitHostPort(address)\n\tif err != nil {\n\t\tipStr = address // OK; probably didn't have a port\n\t}\n\n\t// Some IPv6-Addresses can contain zone identifiers at the end,\n\t// which are separated with \"%\"\n\tzoneID := \"\"\n\tif strings.Contains(ipStr, \"%\") {","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/ip_matchers.go#L290-L326","documentation":"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.","triggerScenarios":"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).","commonSituations":"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).","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."],"exampleFix":"// before\n@lan remote_ip example.com\n\n// after\n@lan remote_ip 192.168.1.10","handlingStrategy":"validation","validationCode":"import \"net/netip\"\n\nfunc validBareIPs(entries []string) bool {\n\tfor _, e := range entries {\n\t\tif strings.Contains(e, \"/\") {\n\t\t\tcontinue\n\t\t}\n\t\tif _, err := netip.ParseAddr(e); err != nil {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["caddy","config","network","ip-address","validation"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}