caddyserver/caddy · error · caddyhttp.Error
cannot use 'copy_response' outside of reverse_proxy's handle
Error message
cannot use 'copy_response' outside of reverse_proxy's handle_response routes
What it means
`copy_response` is a handler that only works inside a `handle_response` route of `reverse_proxy`, because it relies on the upstream response stored in the request context under `proxyHandleResponseContextCtxKey`. When that context value is absent, Caddy returns HTTP 500 with this message.
Source
Thrown at modules/caddyhttp/reverseproxy/copyresponse.go:65
New: func() caddy.Module { return new(CopyResponseHandler) },
}
}
// Provision ensures that h is set up properly before use.
func (h *CopyResponseHandler) Provision(ctx caddy.Context) error {
h.ctx = ctx
return nil
}
// ServeHTTP implements the Handler interface.
func (h CopyResponseHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, _ caddyhttp.Handler) error {
repl := req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
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' outside of reverse_proxy's handle_response routes"))
}
// allow a custom status code to be written; otherwise the
// status code from the upstream response is written
if codeStr := h.StatusCode.String(); codeStr != "" {
intVal, err := strconv.Atoi(repl.ReplaceAll(codeStr, ""))
if err != nil {
return caddyhttp.Error(http.StatusInternalServerError, err)
}
hrc.response.StatusCode = intVal
}
// make sure the reverse_proxy handler doesn't try to call
// finalizeResponse again after we've already done it here.
hrc.isFinalized = true
// write the response
return hrc.handler.finalizeResponse(rw, req, hrc.response, repl, hrc.start, hrc.logger)View on GitHub (pinned to 50e54ee279)
Solutions
- Move copy_response inside a handle_response block of reverse_proxy
- Ensure the route order is: reverse_proxy with handle_response, and copy_response within that block
- If using JSON config, verify the handler appears only under reverse_proxy's handle_response routes
Example fix
// Caddyfile before
example.com {
copy_response
reverse_proxy localhost:9000
}
// Caddyfile after
example.com {
reverse_proxy localhost:9000 {
@error status 500
handle_response @error {
copy_response
}
}
} Defensive patterns
Strategy: validation
Validate before calling
// JSON config guard: copy_response may only appear under
// reverse_proxy.handlers[].handle_response
func containsOnlyInHandleResponse(cfg *Config) bool {
for _, r := range cfg.Routes {
if r.HandlerID == "http.handlers.copy_response" && !r.WithinHandleResponse {
return false
}
}
return true
} Prevention
- Author handle_response trees in the Caddyfile, not raw JSON — the adapter places handlers correctly
- Lint adapted JSON for copy_response handlers not nested under handle_response routes
- Run `caddy validate --config` before deploy to catch structural mistakes early
When it happens
Trigger: Configuring `copy_response` as a top-level handler in a normal `handle` route, or inside `reverse_proxy` outside a `handle_response` block. Also programmatically invoking CopyResponseHandler.ServeHTTP on requests that never traversed reverse_proxy's handle_response machinery.
Common situations: Copy-pasting a handle_response fragment into the main route body, or restructuring a Caddyfile and accidentally moving copy_response outside its handle_response block.
Related errors
- cannot use 'copy_response_headers' outside of reverse_proxy'
- 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/51b10ff84e9febad.
Report an issue: GitHub.