caddyserver/caddy · error

replacement %d for header field '%s': %v

Error message

replacement %d for header field '%s': %v

What it means

Returned during HeaderOps.Provision while precompiling regex replacements: the replacement's search_regexp for a header field failed regexp.Compile. Placeholders like {http.request.uri} inside the regex are skipped (compiled at runtime), but a regex without placeholders must compile now — invalid regex syntax aborts provisioning.

Source

Thrown at modules/caddyhttp/headers/headers.go:154

	if ops == nil {
		return nil // it's possible no ops are configured; fix #6893
	}
	for fieldName, replacements := range ops.Replace {
		for i, r := range replacements {
			if r.SearchRegexp == "" {
				continue
			}

			// Check if it contains placeholders
			if containsPlaceholders(r.SearchRegexp) {
				// Contains placeholders, skips precompilation, and recompiles at runtime
				continue
			}

			// Does not contain placeholders, safe to precompile
			re, err := regexp.Compile(r.SearchRegexp)
			if err != nil {
				return fmt.Errorf("replacement %d for header field '%s': %v", i, fieldName, err)
			}
			replacements[i].re = re
		}
	}
	return nil
}

// containsPlaceholders checks if the string contains Caddy placeholder syntax {key}
func containsPlaceholders(s string) bool {
	_, after, ok := strings.Cut(s, "{")
	if !ok {
		return false
	}
	closeIdx := strings.Index(after, "}")
	if closeIdx == -1 {
		return false
	}
	// Make sure there is content between the brackets

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Copy the exact regex from the error and test it with Go's regexp syntax, e.g. in a scratch Go program or an online RE2 tester.
  2. Remove PCRE-only constructs: backreferences, lookahead/lookbehind; rewrite using RE2-compatible patterns.
  3. Balance groups and escape literal parentheses/brackets.
  4. Validate the whole config with `caddy validate` after fixing.

Example fix

# before (PCRE backreference, invalid in RE2)
replace Host {
    search_regexp "^(www\.)?(.+)$"
    replace "example.com$1"  # intended \2 semantics
}
# after (RE2-safe capture groups)
replace Host {
    search_regexp "^(?:www\.)?(.+)$"
    replace "cdn.example.com${1}"
}
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight in Go: compile every regex before shipping config
for field, reps := range cfg.Replace {
    for i, r := range reps {
        if r.SearchRegexp == "" { continue }
        if _, err := regexp.Compile(r.SearchRegexp); err != nil {
            return fmt.Errorf("field %s replacement %d: %w", field, i, err)
        }
    }
}

Prevention

When it happens

Trigger: Configuring header > replace with a search_regexp containing invalid RE2 syntax, e.g. unbalanced parentheses, backreferences (\1 — unsupported in RE2), or stray quantifiers; the error message includes the field name, replacement index, and compile error.

Common situations: Porting nginx sub_filter or Apache mod_substitute regexes that use PCRE-only features; forgetting Go uses RE2 (no lookaheads/backreferences); escaping mistakes through JSON config layers.

Related errors


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