grpc/grpc-go · error
header key %q is reserved
Error message
header key %q is reserved
What it means
Returned by validateHeaderKey when the key is exactly "host". The host/authority is reserved (it maps to the :authority pseudo-header in HTTP/2), so the transport owns it and an external processor may not mutate it directly. This is the third case in the validation switch.
Source
Thrown at internal/xds/httpfilter/extconfig.go:228
}
continue
}
input.Delete(header)
}
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 {View on GitHub (pinned to 0c51461d27)
Solutions
- Do not mutate the "host" header from an ext_proc server.
- To influence authority, configure route host-rewrite in the xDS RouteConfiguration instead.
- Skip "host" explicitly when building mutations from an inbound header set.
- Document the reserved set (host, :*, grpc-*) in the server's authoring guide.
Example fix
// before
emit("host", newAuthority)
// after: use route rewrite, not header mutation
// (configure virtual host rewrite: request_headers_to_add is NOT for host) Defensive patterns
Strategy: validation
Validate before calling
// server-side: never mutate host
if key == "host" { return /* skip */ } Prevention
- Reserve 'host' as immutable on the server.
- Use xDS route host-rewrite to change authority.
- Skip 'host' in copy-all loops.
- Document host/:authority mapping for authors.
When it happens
Trigger: The ext_proc server returns an add/remove/modify mutation for the literal header name "host" (lowercase). validateHeaderKey matches key == "host" at extconfig.go:227.
Common situations: Server ported from HTTP/1 attempts to rewrite Host; a generic 'copy all headers' loop includes host; a security filter tries to strip host to hide the upstream.
Related errors
- header key %q is a pseudo-header
- header key %q is not lowercase
- 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/52c4cd9bf046de75.
Report an issue: GitHub.