caddyserver/caddy · error
unsupported map key type in header match: %T
Error message
unsupported map key type in header match: %T
What it means
Thrown while converting a CEL-evaluated map into map[string]any for a header match in the CEL matcher. The expression produced a map whose key type is not a Go string (e.g. an int or bool key after ConvertToNative to map[any]any), so the key cannot be used as an HTTP header name. Caddy rejects it because HTTP header names must be strings.
Source
Thrown at modules/caddyhttp/celmatcher.go:686
// of string.
func CELValueToMapStrList(data ref.Val) (map[string][]string, error) {
// Prefer map[string]any, but newer cel-go versions may return map[any]any
mapStrType := reflect.TypeFor[map[string]any]()
mapStrRaw, err := data.ConvertToNative(mapStrType)
var mapStrIface map[string]any
if err != nil {
// Try map[any]any and convert keys to strings
mapAnyType := reflect.TypeFor[map[any]any]()
mapAnyRaw, err2 := data.ConvertToNative(mapAnyType)
if err2 != nil {
return nil, err
}
mapAnyIface := mapAnyRaw.(map[any]any)
mapStrIface = make(map[string]any, len(mapAnyIface))
for k, v := range mapAnyIface {
ks, ok := k.(string)
if !ok {
return nil, fmt.Errorf("unsupported map key type in header match: %T", k)
}
mapStrIface[ks] = v
}
} else {
mapStrIface = mapStrRaw.(map[string]any)
}
mapStrListStr := make(map[string][]string, len(mapStrIface))
for k, v := range mapStrIface {
switch val := v.(type) {
case string:
mapStrListStr[k] = []string{val}
case types.String:
mapStrListStr[k] = []string{string(val)}
case []string:
mapStrListStr[k] = val
case []ref.Val:
convVals := make([]string, len(val))
for i, elem := range val {View on GitHub (pinned to 50e54ee279)
Solutions
- Make every key in the matcher's map a string literal (quote keys: {"Accept": ["text/html"]})
- If generating config from code, serialize maps with string keys only (use map[string][]string in Go)
- Check the CEL expression for computed map constructions and replace them with literal string keys
Example fix
// before
{"expression": "{1: 'v'}.all(k, k)"}
// after
{"expression": "{'accept': 'v'}['accept'] == 'v'"} Defensive patterns
Strategy: validation
Validate before calling
// Go: validate matcher input maps have string keys before building config
func checkKeysString(m any) error {
rv := reflect.ValueOf(m)
if rv.Kind() != reflect.Map || rv.Type().Key().Kind() != reflect.String {
return fmt.Errorf("map keys must be strings, got %v", rv.Type())
}
return nil
} Prevention
- Always quote header names in matcher maps
- Generate configs with map[string][]string in Go rather than any-typed maps
- Run caddy adapt + caddy validate on generated configs in CI
When it happens
Trigger: Writing a CEL expression matcher whose header-map input is built from a map literal with non-string keys, e.g. {header_match: {1: 'v'}} or a map produced by a CEL function returning non-string keys; the code path first tries ConvertToNative(map[string]any), fails, retries map[any]any, and hits a non-string key.
Common situations: Hand-written JSON configs with CEL matchers where map keys were accidentally quoted as numbers/booleans; programmatically generated matcher configs from YAML/JSON tooling that types header names as non-strings.
Related errors
- server %s: %v
- unsupported value type in matcher input: %T
- unsupported element type in matcher input list: %T
- unknown try policy %s
- loading matcher sets: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/fb42ef13308565b2.
Report an issue: GitHub.