caddyserver/caddy · info · HandlerError
%s
Error message
%s
What it means
Not a defect: the static 'error' handler returns the configured message and status code to trigger Caddy's error chain. ServeHTTP resolves the StatusCode string and Error body through the replacer, then returns Error(statusCode, fmt.Errorf("%s", body)). The only genuine failure path is when a placeholder-driven status code resolves to a non-integer, which yields HTTP 500 wrapping strconv.Atoi's error.
Source
Thrown at modules/caddyhttp/staticerror.go:108
default:
return d.Errf("unrecognized subdirective '%s'", d.Val())
}
}
return nil
}
func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
statusCode := http.StatusInternalServerError
if codeStr := e.StatusCode.String(); codeStr != "" {
intVal, err := strconv.Atoi(repl.ReplaceAll(codeStr, ""))
if err != nil {
return Error(http.StatusInternalServerError, err)
}
statusCode = intVal
}
return Error(statusCode, fmt.Errorf("%s", repl.ReplaceKnown(e.Error, "")))
}
// Interface guard
var (
_ MiddlewareHandler = (*StaticError)(nil)
_ caddyfile.Unmarshaler = (*StaticError)(nil)
)
View on GitHub (pinned to 50e54ee279)
Solutions
- If 500 appears unexpectedly: make sure any placeholder in the status code always resolves to a 3-digit integer (or omit it)
- Remember the configured body is returned as an error by design — pair it with handle_errors to render a custom response
- For plain responses without error semantics, use 'respond' instead of 'error'
Example fix
# before
error {http.request.header.x-status} "custom"
# header missing -> Atoi("") fails -> 500
# after
error 403 "forbidden" Defensive patterns
Strategy: validation
Validate before calling
// if the status uses placeholders, guarantee it resolves to an integer:
resolved := repl.ReplaceAll(statusCodeStr, "")
if n, err := strconv.Atoi(resolved); err != nil || n < 100 || n > 599 {
statusCode = http.StatusInternalServerError // explicit fallback, not accidental
} Try / catch
// in handle_errors, inspect the returned error to branch:
handle_errors {
rewrite * /error.html
file_server
} Prevention
- Use literal status codes with the error directive; avoid placeholders unless guaranteed numeric
- Use 'respond' for plain responses; reserve 'error' for triggering error handling
- Add an integration test asserting the final status code of error routes
When it happens
Trigger: Using the 'error' Caddyfile directive or http.handlers.error module: e.g. 'error 403 "forbidden"'. Failing path: statusCode contains a placeholder like {http.error.status} that resolves to non-numeric text at request time.
Common situations: Intentionally aborting requests with a fixed status inside routes; placeholder-based status that is empty or textual; users surprised to see their configured string surface as a Go error in logs.
Related errors
- replacing listen address: %v
- server %s, listener %d: %v
- %s: parsing listen address '%s': %v
- %s: route %d, matcher set %d, matcher %d, host matcher %d: %
- preparing 'ask' endpoint: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/e24723029342d9de.
Report an issue: GitHub.