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 bracketsView on GitHub (pinned to 50e54ee279)
Solutions
- 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.
- Remove PCRE-only constructs: backreferences, lookahead/lookbehind; rewrite using RE2-compatible patterns.
- Balance groups and escape literal parentheses/brackets.
- 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
- Test header replacement regexes with Go's RE2 engine, not PCRE testers.
- Avoid backreferences and lookarounds — unsupported in Go regexp.
- `caddy validate` catches this at config load; make it a CI gate.
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
- cannot specify both a substring search and a regular express
- compiling regexp for mapping %d: %v
- compiling matcher regexp %s: %v
- compiling regular expression %d: %v
- %s: entry %q contains non-ASCII characters
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/e9776df032bcb8ff.
Report an issue: GitHub.