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 = upstreamAddr

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Point h2c:// at the backend's actual cleartext h2c port (commonly 8080 or a custom one).
  2. If the backend serves TLS gRPC on 443, use 'https://backend:443' (with 'transport http { tls }' semantics) instead of h2c.
  3. 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

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


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/7c9eb684613caa1b. Report an issue: GitHub.