grpc/grpc-go · error
header key %q is not lowercase
Error message
header key %q is not lowercase
What it means
Returned by validateHeaderKey when the key is not all-lowercase. HTTP/2 mandates lowercase header names; gRPC carries this requirement through to metadata. validateHeaderKey checks key != strings.ToLower(key) at extconfig.go:231 and rejects mixed/uppercase names like "Content-Type" or "X-Request-Id".
Source
Thrown at internal/xds/httpfilter/extconfig.go:232
}
return nil
}
// validateHeaderKey returns a non-nil error if key may not be mutated by an
// external processing server, either because the key is reserved or because it
// is not a valid gRPC header name.
func validateHeaderKey(key string) error {
switch {
case len(key) == 0:
return fmt.Errorf("header key is empty")
case key[0] == ':':
return fmt.Errorf("header key %q is a pseudo-header", key)
case key == "host":
return fmt.Errorf("header key %q is reserved", key)
case strings.HasPrefix(key, "grpc-"):
return fmt.Errorf("header key %q is in the reserved 'grpc-' space", key)
case key != strings.ToLower(key):
return fmt.Errorf("header key %q is not lowercase", key)
case len(key) > maxHeaderSize:
return fmt.Errorf("header key exceeds the maximum length of %d bytes", maxHeaderSize)
}
return imetadata.ValidateKey(key)
}
func (hmr *HeaderMutationRules) allow(key string) bool {
if hmr.DisallowExpr != nil && hmr.DisallowExpr.MatchString(key) {
return false
}
if hmr.AllowExpr != nil && hmr.AllowExpr.MatchString(key) {
return true
}
if hmr.AllowExpr != nil {
return false
}
return true
}View on GitHub (pinned to 0c51461d27)
Solutions
- Lowercase the key on the ext_proc server before emitting the mutation.
- Normalize at the boundary where external data enters the server (strings.ToLower).
- Add a lint check / unit test asserting all emitted keys are lowercase.
- Audit shared header constants for accidental uppercase.
Example fix
// before
emit("X-Request-Id", id)
// after
emit(strings.ToLower("X-Request-Id"), id) // -> "x-request-id" Defensive patterns
Strategy: validation
Validate before calling
// server-side: lowercase keys at the boundary key = strings.ToLower(key)
Prevention
- Lowercase all header keys before emitting mutations.
- Normalize at the data-ingress boundary of the server.
- Lint constants for accidental uppercase.
- Test against a known Title-Case input.
When it happens
Trigger: The ext_proc server returns a mutation whose key contains uppercase letters (e.g. "Content-Type", "X-Custom"). validateHeaderKey fails the lowercase check before grammar validation.
Common situations: Server reused from HTTP/1 code where header case was free; server copies headers from a case-preserving map without normalizing; integration with a system that canonicalizes header names to Title-Case.
Related errors
- header key %q is a pseudo-header
- header key %q is reserved
- invalid header mutation: value for header key %q exceeds the
- header mutation disallowed by headerMutationRules for header
- header mutation disallowed by headerMutationRules for header
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/35e0f17cb9c4f06b.
Report an issue: GitHub.