caddyserver/caddy · error
server %s: %v
Error message
server %s: %v
What it means
Thrown during App.Provision while validating a server's ExpectedUnderscoreHeaders allowlist via provisionUnderscoreHeaders (modules/caddyhttp/server.go:434). Each entry is compiled into exact/prefix allow-and-drop rules for the hot-path header filter; a malformed entry (bad header name, bad 'drop' form, invalid prefix rule) aborts provisioning of that server. The %s is the server name, %v the underlying validation error.
Source
Thrown at modules/caddyhttp/app.go:307
}
// set up the trusted proxies source
for srv.TrustedProxiesRaw != nil {
val, err := ctx.LoadModule(srv, "TrustedProxiesRaw")
if err != nil {
return fmt.Errorf("loading trusted proxies modules: %v", err)
}
srv.trustedProxies = val.(IPRangeSource)
}
// set the default client IP header to read from
if srv.ClientIPHeaders == nil {
srv.ClientIPHeaders = []string{"X-Forwarded-For"}
}
// precompute underscore and dot header allowlist rules
if err := srv.provisionUnderscoreHeaders(); err != nil {
return fmt.Errorf("server %s: %v", srvName, err)
}
if err := srv.provisionDotHeaders(); err != nil {
return fmt.Errorf("server %s: %v", srvName, err)
}
// process each listener address
for i := range srv.Listen {
lnOut, err := repl.ReplaceOrErr(srv.Listen[i], true, true)
if err != nil {
return fmt.Errorf("server %s, listener %d: %v", srvName, i, err)
}
srv.Listen[i] = lnOut
}
// set up each listener modifier
if srv.ListenerWrappersRaw != nil {
vals, err := ctx.LoadModule(srv, "ListenerWrappersRaw")
if err != nil {View on GitHub (pinned to 50e54ee279)
Solutions
- Read the wrapped %v message: it names the exact offending entry in expected_underscore_headers
- Correct or remove that entry so every header name is a valid token using underscores as written by the client
- Reload/validate the config with `caddy validate --config <file>` before applying
Example fix
// before "expected_underscore_headers": ["X_Custom_Header", ""] // after "expected_underscore_headers": ["X_Custom_Header"]
Defensive patterns
Strategy: validation
Validate before calling
// before loading: ensure each entry is a non-empty valid header token
for _, h := range srvCfg.ExpectedUnderscoreHeaders {
if h == "" || strings.ContainsAny(h, " \t:;,") {
return fmt.Errorf("bad expected_underscore_headers entry: %q", h)
}
} Prevention
- Generate expected_underscore_headers entries from a validated list of header names, never hand-edit JSON
- Run `caddy validate --config` in CI for every config change
- Keep a golden adapted-JSON config in the repo and diff against it
When it happens
Trigger: Setting servers.<name>.expected_underscore_headers in the JSON config (or the Caddyfile equivalent) with an entry that provisionHeaderAliasAllowlist rejects: empty name, wrong separator character, or an invalid prefix/drop combination. Any single bad entry fails the whole server.
Common situations: Copying header names from an upstream proxy config that uses dots or hyphens instead of underscores; adding a prefix wildcard rule with the wrong syntax after upgrading Caddy to a version with header-alias allowlists; trailing whitespace in header names.
Related errors
- server %s: setting up route handlers: %v
- server %s: setting up error handling routes: %v
- server %s: setting up named route '%s' handlers: %v
- unknown object ID '%s'
- loading new config: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/6aaf56b4e80da8e8.
Report an issue: GitHub.