caddyserver/caddy · error · caddyhttp.Error

cannot use 'copy_response_headers' outside of reverse_proxy'

Error message

cannot use 'copy_response_headers' outside of reverse_proxy's handle_response routes

What it means

`copy_response_headers` reads the upstream response from the request context key `proxyHandleResponseContextCtxKey`, which is only set inside reverse_proxy's handle_response routes. Used elsewhere, the type assertion fails and the request gets a 500 with this message.

Source

Thrown at modules/caddyhttp/reverseproxy/copyresponse.go:151

	// Optimize the exclude list by converting it to a map
	if len(h.Exclude) > 0 {
		h.excludeMap = map[string]struct{}{}
	}
	for _, field := range h.Exclude {
		h.excludeMap[http.CanonicalHeaderKey(field)] = struct{}{}
	}

	return nil
}

// ServeHTTP implements the Handler interface.
func (h CopyResponseHeadersHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next caddyhttp.Handler) error {
	hrc, ok := req.Context().Value(proxyHandleResponseContextCtxKey).(*handleResponseContext)

	// don't allow this to be used outside of handle_response routes
	if !ok {
		return caddyhttp.Error(http.StatusInternalServerError,
			fmt.Errorf("cannot use 'copy_response_headers' outside of reverse_proxy's handle_response routes"))
	}

	for field, values := range hrc.response.Header {
		// Check the include list first, skip
		// the header if it's _not_ in this list.
		if len(h.includeMap) > 0 {
			if _, ok := h.includeMap[field]; !ok {
				continue
			}
		}

		// Then, check the exclude list, skip
		// the header if it _is_ in this list.
		if len(h.excludeMap) > 0 {
			if _, ok := h.excludeMap[field]; ok {
				continue
			}
		}

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Move copy_response_headers into a handle_response block within reverse_proxy
  2. Use the plain `header` directive with deletion/setting if you want to manipulate headers outside handle_response
  3. Validate the config path: caddy adapt --config to inspect where the handler ended up in JSON

Example fix

# before
example.com {
	reverse_proxy localhost:9000 {
		handle_response {
			copy_response
		}
	}
	copy_response_headers {
		include Content-Type
	}
}
# after
example.com {
	reverse_proxy localhost:9000 {
		handle_response {
			copy_response
			copy_response_headers {
				include Content-Type
			}
		}
	}
}
Defensive patterns

Strategy: validation

Validate before calling

// Guard: copy_response_handlers must sit inside handle_response
for _, h := range route.Handlers {
	if h.ID == "http.handlers.copy_response_headers" && !route.IsHandleResponse {
		return errors.New("copy_response_headers outside handle_response")
	}
}

Prevention

When it happens

Trigger: Placing copy_response_headers in a regular route (not inside reverse_proxy's handle_response), or dispatching requests to CopyResponseHeadersHandler.ServeHTTP directly without the reverse_proxy response context.

Common situations: Refactoring a Caddyfile and moving the copy_response_headers block into the main route; assuming it works like the generic `header` directive.

Related errors


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