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
- Move copy_response_headers into a handle_response block within reverse_proxy
- Use the plain `header` directive with deletion/setting if you want to manipulate headers outside handle_response
- 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
- Use the Caddyfile form so handle_response structure is enforced by the adapter
- Run `caddy adapt` and inspect the JSON nesting of copy_* handlers before deploy
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
- cannot use 'copy_response' outside of reverse_proxy's handle
- cannot define both 'exclude' and 'include' lists at the same
- a cycle of imports exists between %s and %s
- consolidating TLS connection policies for server %d: %v
- applying global server options: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/39d69b9a564598e2.
Report an issue: GitHub.