{"record":{"id":"43ec7d1a8525f6fa","repo":"caddyserver/caddy","slug":"parsing-upstream-url-parse-v-port-range-inval","errorCode":null,"errorMessage":"parsing upstream URL: parse \"%v\": port range invalid: %v","messagePattern":"parsing upstream URL: parse \"(.+?)\": port range invalid: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddyhttp/reverseproxy/addresses.go","lineNumber":88,"sourceCode":"\t\t// so we return a more user-friendly error message instead\n\t\t// to explain what to do instead\n\t\tif strings.Contains(upstreamAddr, \"{\") {\n\t\t\treturn parsedAddr{}, fmt.Errorf(\"due to parsing difficulties, placeholders are not allowed when an upstream address contains a scheme\")\n\t\t}\n\n\t\ttoURL, err := url.Parse(upstreamAddr)\n\t\tif err != nil {\n\t\t\t// if the error seems to be due to a port range,\n\t\t\t// try to replace the port range with a dummy\n\t\t\t// single port so that url.Parse() will succeed\n\t\t\tif strings.Contains(err.Error(), \"invalid port\") && strings.Contains(err.Error(), \"-\") {\n\t\t\t\tindex := strings.LastIndex(upstreamAddr, \":\")\n\t\t\t\tif index == -1 {\n\t\t\t\t\treturn parsedAddr{}, fmt.Errorf(\"parsing upstream URL: %v\", err)\n\t\t\t\t}\n\t\t\t\tportRange := upstreamAddr[index+1:]\n\t\t\t\tif strings.Count(portRange, \"-\") != 1 {\n\t\t\t\t\treturn parsedAddr{}, fmt.Errorf(\"parsing upstream URL: parse \\\"%v\\\": port range invalid: %v\", upstreamAddr, portRange)\n\t\t\t\t}\n\t\t\t\ttoURL, err = url.Parse(strings.ReplaceAll(upstreamAddr, portRange, \"0\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn parsedAddr{}, fmt.Errorf(\"parsing upstream URL: %v\", err)\n\t\t\t\t}\n\t\t\t\tport = portRange\n\t\t\t} else {\n\t\t\t\treturn parsedAddr{}, fmt.Errorf(\"parsing upstream URL: %v\", err)\n\t\t\t}\n\t\t}\n\t\tif port == \"\" {\n\t\t\tport = toURL.Port()\n\t\t}\n\n\t\t// there is currently no way to perform a URL rewrite between choosing\n\t\t// a backend and proxying to it, so we cannot allow extra components\n\t\t// in backend URLs\n\t\tif toURL.Path != \"\" || toURL.RawQuery != \"\" || toURL.Fragment != \"\" {","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/reverseproxy/addresses.go#L70-L106","documentation":"url.Parse failed with an 'invalid port' error containing '-', so Caddy assumed a port range and replaced everything after the last ':' with a dummy port — but the extracted port range does not contain exactly one '-' (strings.Count(portRange, \"-\") != 1). Only a single contiguous range like '8080-8090' is supported.","triggerScenarios":"Upstream addresses such as 'http://host:8080-8090-8100' (two dashes), 'http://host:-8080' (leading dash), or where the last segment after the colon coincidentally contains multiple dashes, e.g. IPv6 or hyphenated hostnames misparsed as ports.","commonSituations":"Trying to express multiple discontiguous port ranges in one upstream; hyphenated hostnames where the colon detection lands in the wrong place.","solutions":["Use a single contiguous port range: 'http://host:8080-8090'.","Split discontiguous ranges into multiple upstreams: 'reverse_proxy host:8080-8090 host:9090'.","Wrap IPv6 literals in brackets so the last colon is the port separator: 'http://[fe80::1]:8080'."],"exampleFix":"# before\nreverse_proxy http://backend:8080-8090-8100\n\n# after\nreverse_proxy http://backend:8080-8090 http://backend:8100","handlingStrategy":"validation","validationCode":"func validPortRange(addr string) bool {\n\ti := strings.LastIndex(addr, \":\")\n\tif i < 0 || i == len(addr)-1 {\n\t\treturn false\n\t}\n\tpr := addr[i+1:]\n\treturn strings.Count(pr, \"-\") == 1 && pr[0] != '-'\n}\n\nfor _, u := range upstreams {\n\tif strings.Contains(u, \"://\") && strings.Contains(u, \"-\") && !validPortRange(u) {\n\t\treturn fmt.Errorf(\"upstream %q: only single contiguous port ranges (start-end) allowed\", u)\n\t}\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Express discontiguous port sets as multiple upstreams, never multiple dashes.","Keep port ranges as low-high with low <= high.","Bracket IPv6 hosts so the last colon is the port separator."],"tags":["reverse-proxy","port-range","upstream","configuration"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}