caddyserver/caddy · error
upstream address has conflicting scheme (http://) and port (
Error message
upstream address has conflicting scheme (http://) and port (:443, the HTTPS port)
What it means
The upstream declares scheme http:// (cleartext) but an explicit port 443 (HTTPS default). Caddy treats this as a probable misconfiguration and refuses to start rather than silently sending plaintext HTTP to a TLS listener.
Source
Thrown at modules/caddyhttp/reverseproxy/addresses.go:112
port = portRange
} else {
return parsedAddr{}, fmt.Errorf("parsing upstream URL: %v", err)
}
}
if port == "" {
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"
}
}
View on GitHub (pinned to 50e54ee279)
Solutions
- If the backend is TLS-enabled, use 'https://backend:443' (or just 'https://backend').
- If the backend really serves plaintext on 443, pick the correct port or drop the scheme so the conflict disappears.
- Re-validate after the change: 'caddy validate --config <file>'.
Example fix
# before reverse_proxy http://backend:443 # after reverse_proxy https://backend:443
Defensive patterns
Strategy: validation
Validate before calling
var tlsPorts = map[string]bool{"443": true}
func schemePortConflict(u *url.URL) bool {
return (u.Scheme == "http" || u.Scheme == "h2c") && tlsPorts[u.Port()]
}
if parsed, err := url.Parse(upstream); err == nil && schemePortConflict(parsed) {
return fmt.Errorf("upstream %q pairs a cleartext scheme with the HTTPS port", upstream)
} Prevention
- Keep scheme and port semantically aligned: http/h2c -> non-443, https -> 443/custom TLS port.
- When a backend moves to TLS, change the scheme in the same commit as the port.
- CI-validate configs to catch drift between scheme and port.
When it happens
Trigger: 'reverse_proxy http://backend:443' or 'caddy reverse-proxy --from x --to http://host:443'.
Common situations: Forgetting to change the scheme when the backend was upgraded to TLS, or assuming Caddy upgrades the connection; also copying a port from an https:// URL while typing http://.
Related errors
- upstream address has conflicting scheme (https://) and port
- due to parsing difficulties, placeholders are not allowed wh
- parsing upstream URL: %v
- parsing upstream URL: parse "%v": port range invalid: %v
- for now, URLs for proxy upstreams only support scheme, host,
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/c432c5ae616e95b9.
Report an issue: GitHub.