caddyserver/caddy · error
marshaling matcher set %#v: %v
Error message
marshaling matcher set %#v: %v
What it means
encodeMatcherSet json.Marshal's each request matcher value (map[string]caddyhttp.RequestMatcherWithError) when encoding matcher sets. This only fails if a matcher type carries fields json.Marshal cannot represent (MarshalJSON returning an error, unsupported map key types, channels/funcs). Core matchers never trigger it; a misbehaving plugin matcher can.
Source
Thrown at caddyconfig/httpcaddyfile/httptype.go:1764
for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
matcherName := d.Val()
tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
}
for matcherName, tokens := range tokensByMatcherName {
err := makeMatcher(matcherName, tokens)
if err != nil {
return err
}
}
return nil
}
func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcherWithError) (caddy.ModuleMap, error) {
msEncoded := make(caddy.ModuleMap)
for matcherName, val := range matchers {
jsonBytes, err := json.Marshal(val)
if err != nil {
return nil, fmt.Errorf("marshaling matcher set %#v: %v", matchers, err)
}
msEncoded[matcherName] = jsonBytes
}
return msEncoded, nil
}
// WasReplacedPlaceholderShorthand checks if a token string was
// likely a replaced shorthand of the known Caddyfile placeholder
// replacement outputs. Useful to prevent some user-defined map
// output destinations from overlapping with one of the
// predefined shorthands.
func WasReplacedPlaceholderShorthand(token string) string {
prev := ""
for i, item := range placeholderShorthands() {
// only look at every 2nd item, which is the replacement
if i%2 == 0 {
prev = item
continueView on GitHub (pinned to 50e54ee279)
Solutions
- Inspect the matcher type named in the failing set and find non-serializable exported fields; replace funcs/chans with serializable config (e.g. IDs resolved at Provision time).
- Implement json.Marshaler on the matcher to control serialization.
- Reproduce with 'caddy adapt --config ... --adapter caddyfile' to iterate quickly without running the server.
- Report/patch the plugin if you are only a consumer of it.
Example fix
// before
type MatchThing struct {
Check func(*http.Request) bool `json:"-"` // exported func field: Marshal refuses
}
// after
type MatchThing struct {
CheckExpr string `json:"check_expr"` // serializable config; compiled in Provision
} Defensive patterns
Strategy: validation
Validate before calling
// in plugin tests: assert the matcher marshals cleanly
b, err := json.Marshal(matcher)
if err != nil {
t.Fatalf("matcher not serializable: %v", err)
} Prevention
- Unit-test JSON marshaling of every exported matcher config in plugin CI.
- Keep runtime state (funcs, chans) unexported or tagged json:"-".
When it happens
Trigger: A plugin RequestMatcher with an exported field of type func(), chan, complex, or non-string map keys that json.Marshal rejects; a custom MarshalJSON on the matcher that returns an error under some inputs.
Common situations: Developing a plugin matcher that stores callbacks or unexported runtime state in exported fields; matchers embedding non-serializable third-party structs.
Related errors
- matcher module '%s' is not a Caddyfile unmarshaler
- matcher module '%s' is not a request matcher
- matcher module '%s' is not a request matcher
- marshaling %T matcher: %v
- server block %v: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/6118eb5cd52de802.
Report an issue: GitHub.