grpc/grpc-go · critical
received an illegal stream id: %v. headers frame: %+v
Error message
received an illegal stream id: %v. headers frame: %+v
What it means
This error occurs in operateHeaders when an incoming HEADERS frame has a stream ID that is either even (client-initiated streams must use odd IDs per HTTP/2 spec) or less than or equal to the maximum stream ID already seen (streams must be monotonically increasing). This is treated as a protocol violation and triggers a GOAWAY with PROTOCOL_ERROR.
Source
Thrown at internal/transport/http2_server.go:398
defer t.maxStreamMu.Unlock()
streamID := frame.Header().StreamID
// frame.Truncated is set to true when framer detects that the current header
// list size hits MaxHeaderListSize limit.
if frame.Truncated {
t.controlBuf.put(&cleanupStream{
streamID: streamID,
rst: true,
rstCode: http2.ErrCodeFrameSize,
onWrite: func() {},
})
return nil
}
if streamID%2 != 1 || streamID <= t.maxStreamID {
// illegal gRPC stream id.
return fmt.Errorf("received an illegal stream id: %v. headers frame: %+v", streamID, frame)
}
t.maxStreamID = streamID
s := &ServerStream{
Stream: Stream{
id: streamID,
fc: inFlow{limit: uint32(t.initialWindowSize)},
},
st: t,
headerWireLength: int(frame.Header().Length),
}
s.Stream.buf.init()
var (
// if false, content-type was missing or invalid
isGRPC = false
contentType = ""
mdata = make(metadata.MD, len(frame.Fields))
httpMethod stringView on GitHub (pinned to 03255a9237)
Solutions
- Identify the client library/implementation generating invalid stream IDs — this is a client-side protocol bug.
- Upgrade the client's HTTP/2 or gRPC library to a compliant version.
- If behind a proxy/L7 load balancer, verify it correctly forwards HTTP/2 stream IDs.
- Check for concurrent connections being multiplexed incorrectly by an intermediary.
Example fix
// No client-side code fix — this is a protocol-level violation by the peer. // Ensure the client uses a compliant gRPC/HTTP2 library: // before (broken): custom/raw HTTP2 client with bad stream ID management // (even or reused stream IDs) // after (valid): use grpc-go or another compliant gRPC client library conn, err := grpc.Dial(target, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
Defensive patterns
Strategy: validation
Try / catch
// This error causes a GOAWAY — the connection is terminated.
// On the client side, handle connection-level errors with reconnection:
if status.Code(err) == codes.Unavailable {
if strings.Contains(err.Error(), "GOAWAY") || strings.Contains(err.Error(), "protocol") {
// gRPC client will automatically reconnect via the name resolver
log.Printf("connection terminated due to protocol violation")
}
} Prevention
- Use compliant gRPC client libraries to avoid generating invalid stream IDs.
- Avoid custom/raw HTTP/2 implementations for gRPC traffic.
- Monitor server logs for GOAWAY/protocol errors to identify problematic clients.
- Keep client gRPC library versions up to date.
When it happens
Trigger: A client sends a HEADERS frame with an even stream ID, or reuses/collides with a stream ID lower than a previously established stream. The server detects the violation in operateHeaders and returns the error which causes a connection-level GOAWAY.
Common situations: Buggy or non-compliant HTTP/2 client library that generates incorrect stream IDs, a client implementing HTTP/2 multiplexing incorrectly, connection hijacking/probing by a non-gRPC client, or a badly behaving intermediary/proxy that reuses stream IDs.
Related errors
- transport: timeout string is too long: %q
- transport: timeout unit is not recognized: %q
- transport: timeout string is too short: %q
- received %d-bytes data exceeding the limit %d bytes
- keepalive ping not acked within timeout %s
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/77b161809abeaf64.
Report an issue: GitHub.