caddyserver/caddy · error
upstream address has conflicting scheme (h2c://) and port (:
Error message
upstream address has conflicting scheme (h2c://) and port (:443, the HTTPS port)
What it means
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.
Source
Thrown at modules/caddyhttp/reverseproxy/addresses.go:118
port = toURL.Port()
}
// there is currently no way to perform a URL rewrite between choosing
// a backend and proxying to it, so we cannot allow extra components
// in backend URLs
if toURL.Path != "" || toURL.RawQuery != "" || toURL.Fragment != "" {
return parsedAddr{}, fmt.Errorf("for now, URLs for proxy upstreams only support scheme, host, and port components")
}
// ensure the port and scheme aren't in conflict
if toURL.Scheme == "http" && port == "443" {
return parsedAddr{}, fmt.Errorf("upstream address has conflicting scheme (http://) and port (:443, the HTTPS port)")
}
if toURL.Scheme == "https" && port == "80" {
return parsedAddr{}, fmt.Errorf("upstream address has conflicting scheme (https://) and port (:80, the HTTP port)")
}
if toURL.Scheme == "h2c" && port == "443" {
return parsedAddr{}, fmt.Errorf("upstream address has conflicting scheme (h2c://) and port (:443, the HTTPS port)")
}
// if port is missing, attempt to infer from scheme
if port == "" {
switch toURL.Scheme {
case "", "http", "h2c":
port = "80"
case "https":
port = "443"
}
}
scheme, host = toURL.Scheme, toURL.Hostname()
} else {
var err error
network, host, port, err = caddy.SplitNetworkAddress(upstreamAddr)
if err != nil {
host = upstreamAddrView on GitHub (pinned to 50e54ee279)
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.
Example fix
# before reverse_proxy h2c://grpc-backend:443 # after reverse_proxy h2c://grpc-backend:8080
Defensive patterns
Strategy: validation
Validate before calling
func h2cOnTlsPort(u *url.URL) bool {
return u.Scheme == "h2c" && u.Port() == "443"
}
if parsed, err := url.Parse(upstream); err == nil && h2cOnTlsPort(parsed) {
return fmt.Errorf("upstream %q pairs h2c:// with port 443; use https:// for TLS gRPC", upstream)
} Prevention
- 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.
When it happens
Trigger: 'reverse_proxy h2c://backend:443'.
Common situations: Running gRPC (h2c) backends and copying the 443 port from the public listener instead of the internal h2c port.
Related errors
- upstream address has conflicting scheme (http://) and port (
- upstream address has conflicting scheme (https://) and port
- cannot reuse socket %v: %w
- due to parsing difficulties, placeholders are not allowed wh
- parsing upstream URL: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/7c9eb684613caa1b.
Report an issue: GitHub.