caddyserver/caddy · error

for now, URLs for proxy upstreams only support scheme, host,

Error message

for now, URLs for proxy upstreams only support scheme, host, and port components

What it means

The upstream address parsed successfully but contains URL components Caddy cannot honor: a path, query string, or fragment (e.g. 'http://host/base?x=1#f'). Because there is no rewrite step between upstream selection and dialing, Caddy only accepts scheme, host, and port in upstream addresses.

Source

Thrown at modules/caddyhttp/reverseproxy/addresses.go:107

				}
				toURL, err = url.Parse(strings.ReplaceAll(upstreamAddr, portRange, "0"))
				if err != nil {
					return parsedAddr{}, fmt.Errorf("parsing upstream URL: %v", err)
				}
				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"

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Strip path/query/fragment from the upstream: 'reverse_proxy http://backend'.
  2. If you need to change the request path for the backend, use 'rewrite * /api/v1{uri}' before reverse_proxy.
  3. To forward a query-based token, set it with 'header_up' instead of embedding it in the upstream URL.

Example fix

# before
reverse_proxy http://backend/api/v1

# after
handle {
  rewrite * /api/v1{uri}
  reverse_proxy http://backend
}
Defensive patterns

Strategy: validation

Validate before calling

if u, err := url.Parse(upstream); err == nil {
    if u.Path != "" || u.RawQuery != "" || u.Fragment != "" {
        return fmt.Errorf("upstream %q must not contain path/query/fragment", upstream)
    }
}

Prevention

When it happens

Trigger: Configuring 'reverse_proxy http://backend/api/v1' or 'http://backend?token=abc'; also 'caddy reverse-proxy --to http://host/path'.

Common situations: Assuming reverse_proxy path-appends like a redirect target; pasting full API URLs from documentation instead of bare host:port.

Related errors


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