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

  1. Read the wrapped %v message: it names the exact offending entry in expected_underscore_headers
  2. Correct or remove that entry so every header name is a valid token using underscores as written by the client
  3. 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

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


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/6aaf56b4e80da8e8. Report an issue: GitHub.