{"record":{"id":"6aaf56b4e80da8e8","repo":"caddyserver/caddy","slug":"server-s-v","errorCode":null,"errorMessage":"server %s: %v","messagePattern":"server (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddyhttp/app.go","lineNumber":307,"sourceCode":"\t\t}\n\n\t\t// set up the trusted proxies source\n\t\tfor srv.TrustedProxiesRaw != nil {\n\t\t\tval, err := ctx.LoadModule(srv, \"TrustedProxiesRaw\")\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"loading trusted proxies modules: %v\", err)\n\t\t\t}\n\t\t\tsrv.trustedProxies = val.(IPRangeSource)\n\t\t}\n\n\t\t// set the default client IP header to read from\n\t\tif srv.ClientIPHeaders == nil {\n\t\t\tsrv.ClientIPHeaders = []string{\"X-Forwarded-For\"}\n\t\t}\n\n\t\t// precompute underscore and dot header allowlist rules\n\t\tif err := srv.provisionUnderscoreHeaders(); err != nil {\n\t\t\treturn fmt.Errorf(\"server %s: %v\", srvName, err)\n\t\t}\n\t\tif err := srv.provisionDotHeaders(); err != nil {\n\t\t\treturn fmt.Errorf(\"server %s: %v\", srvName, err)\n\t\t}\n\n\t\t// process each listener address\n\t\tfor i := range srv.Listen {\n\t\t\tlnOut, err := repl.ReplaceOrErr(srv.Listen[i], true, true)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"server %s, listener %d: %v\", srvName, i, err)\n\t\t\t}\n\t\t\tsrv.Listen[i] = lnOut\n\t\t}\n\n\t\t// set up each listener modifier\n\t\tif srv.ListenerWrappersRaw != nil {\n\t\t\tvals, err := ctx.LoadModule(srv, \"ListenerWrappersRaw\")\n\t\t\tif err != nil {","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/app.go#L289-L325","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n\"expected_underscore_headers\": [\"X_Custom_Header\", \"\"]\n// after\n\"expected_underscore_headers\": [\"X_Custom_Header\"]","handlingStrategy":"validation","validationCode":"// before loading: ensure each entry is a non-empty valid header token\nfor _, h := range srvCfg.ExpectedUnderscoreHeaders {\n    if h == \"\" || strings.ContainsAny(h, \" \\t:;,\") {\n        return fmt.Errorf(\"bad expected_underscore_headers entry: %q\", h)\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["caddy","http","headers","provisioning","config"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}