{"record":{"id":"a744f2a949014aa6","repo":"caddyserver/caddy","slug":"due-to-parsing-difficulties-placeholders-are-not","errorCode":null,"errorMessage":"due to parsing difficulties, placeholders are not allowed when an upstream address contains a scheme","messagePattern":"due to parsing difficulties, placeholders are not allowed when an upstream address contains a scheme","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddyhttp/reverseproxy/addresses.go","lineNumber":73,"sourceCode":"}\n\nfunc (p parsedAddr) isUnix() bool {\n\treturn caddy.IsUnixNetwork(p.network)\n}\n\n// parseUpstreamDialAddress parses configuration inputs for\n// the dial address, including support for a scheme in front\n// as a shortcut for the port number, and a network type,\n// for example 'unix' to dial a unix socket.\nfunc parseUpstreamDialAddress(upstreamAddr string) (parsedAddr, error) {\n\tvar network, scheme, host, port string\n\n\tif strings.Contains(upstreamAddr, \"://\") {\n\t\t// we get a parsing error if a placeholder is specified\n\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 {","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/reverseproxy/addresses.go#L55-L91","documentation":"parseUpstreamDialAddress rejects upstream addresses that contain both a scheme (detected via '://') and a '{' placeholder character. url.Parse cannot handle Caddy placeholders, so instead of an opaque parse error Caddy fails fast with this explicit message telling you the two features are mutually exclusive.","triggerScenarios":"Configuring a reverse_proxy upstream like 'https://{env.BACKEND_HOST}' or 'http://{upstream_host}:8080' where the address string contains both a scheme and any placeholder.","commonSituations":"Trying to select the backend scheme from environment variables or request-derived placeholders; forgetting that the port can be inferred from the scheme and that placeholders work only in scheme-less upstream addresses.","solutions":["Remove the scheme and keep the placeholder, e.g. 'reverse_proxy {env.BACKEND_HOST}:443' (or use transport block with tls for HTTPS semantics).","If the scheme must vary, resolve the full address (scheme included is still impossible; resolve host+port) outside Caddy via environment variables per deployment.","Use dynamic upstream modules (e.g. 'dynamic srv' or 'dynamic a') when the backend set must be computed at runtime."],"exampleFix":"# before (Caddyfile)\nreverse_proxy https://{env.BACKEND_HOST}\n\n# after\nreverse_proxy {env.BACKEND_HOST}:443 {\n  transport http {\n    tls\n  }\n}","handlingStrategy":"validation","validationCode":"func hasSchemePlaceholder(addr string) bool {\n\treturn strings.Contains(addr, \"://\") && strings.Contains(addr, \"{\")\n}\n\nfor _, u := range cfg.Upstreams {\n\tif hasSchemePlaceholder(u.Dial) {\n\t\treturn fmt.Errorf(\"upstream %q mixes a scheme with a placeholder; move the port into the value or use a transport block\", u.Dial)\n\t}\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep upstream addresses as host[:port] and express TLS via 'transport http { tls }'.","Resolve env vars to full host:port strings outside the config.","Assert generated configs contain no '{' in scheme-bearing upstreams before caddy.Run."],"tags":["reverse-proxy","upstream","placeholders","configuration"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}