{"record":{"id":"7c9eb684613caa1b","repo":"caddyserver/caddy","slug":"upstream-address-has-conflicting-scheme-h2c-a","errorCode":null,"errorMessage":"upstream address has conflicting scheme (h2c://) and port (:443, the HTTPS port)","messagePattern":"upstream address has conflicting scheme \\(h2c://\\) and port \\(:443, the HTTPS port\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddyhttp/reverseproxy/addresses.go","lineNumber":118,"sourceCode":"\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 != \"\" {\n\t\t\treturn parsedAddr{}, fmt.Errorf(\"for now, URLs for proxy upstreams only support scheme, host, and port components\")\n\t\t}\n\n\t\t// ensure the port and scheme aren't in conflict\n\t\tif toURL.Scheme == \"http\" && port == \"443\" {\n\t\t\treturn parsedAddr{}, fmt.Errorf(\"upstream address has conflicting scheme (http://) and port (:443, the HTTPS port)\")\n\t\t}\n\t\tif toURL.Scheme == \"https\" && port == \"80\" {\n\t\t\treturn parsedAddr{}, fmt.Errorf(\"upstream address has conflicting scheme (https://) and port (:80, the HTTP port)\")\n\t\t}\n\t\tif toURL.Scheme == \"h2c\" && port == \"443\" {\n\t\t\treturn parsedAddr{}, fmt.Errorf(\"upstream address has conflicting scheme (h2c://) and port (:443, the HTTPS port)\")\n\t\t}\n\n\t\t// if port is missing, attempt to infer from scheme\n\t\tif port == \"\" {\n\t\t\tswitch toURL.Scheme {\n\t\t\tcase \"\", \"http\", \"h2c\":\n\t\t\t\tport = \"80\"\n\t\t\tcase \"https\":\n\t\t\t\tport = \"443\"\n\t\t\t}\n\t\t}\n\n\t\tscheme, host = toURL.Scheme, toURL.Hostname()\n\t} else {\n\t\tvar err error\n\t\tnetwork, host, port, err = caddy.SplitNetworkAddress(upstreamAddr)\n\t\tif err != nil {\n\t\t\thost = upstreamAddr","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/reverseproxy/addresses.go#L100-L136","documentation":"The upstream declares scheme h2c:// (cleartext HTTP/2) but an explicit port 443 (HTTPS default). Caddy applies the same scheme/port sanity check to h2c as to http since both are non-TLS transports, and refuses the combination.","triggerScenarios":"'reverse_proxy h2c://backend:443'.","commonSituations":"Running gRPC (h2c) backends and copying the 443 port from the public listener instead of the internal h2c port.","solutions":["Point h2c:// at the backend's actual cleartext h2c port (commonly 8080 or a custom one).","If the backend serves TLS gRPC on 443, use 'https://backend:443' (with 'transport http { tls }' semantics) instead of h2c.","Omit the port and let Caddy infer 80 for h2c when appropriate."],"exampleFix":"# before\nreverse_proxy h2c://grpc-backend:443\n\n# after\nreverse_proxy h2c://grpc-backend:8080","handlingStrategy":"validation","validationCode":"func h2cOnTlsPort(u *url.URL) bool {\n    return u.Scheme == \"h2c\" && u.Port() == \"443\"\n}\n\nif parsed, err := url.Parse(upstream); err == nil && h2cOnTlsPort(parsed) {\n    return fmt.Errorf(\"upstream %q pairs h2c:// with port 443; use https:// for TLS gRPC\", upstream)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["For gRPC over TLS use https:// upstreams; reserve h2c:// for cleartext ports.","Record each gRPC backend's transport (h2c vs TLS) next to its port in service docs.","Let Caddy infer the port from the scheme when unsure."],"tags":["reverse-proxy","h2c","grpc","port-conflict"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}