grpc/grpc-go · error
gRPC requires HTTP/2
Error message
gRPC requires HTTP/2
What it means
gRPC requires HTTP/2. NewServerHandlerTransport checks r.ProtoMajor == 2 and returns HTTP 505 HTTP Version Not Supported for any HTTP/1.x request. The underlying http.Server must be configured to negotiate HTTP/2.
Source
Thrown at internal/transport/handler_server.go:70
// It requires that the http Server supports HTTP/2.
func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request, stats stats.Handler, bufferPool mem.BufferPool) (ServerTransport, error) {
if r.Method != http.MethodPost {
w.Header().Set("Allow", http.MethodPost)
msg := fmt.Sprintf("invalid gRPC request method %q", r.Method)
http.Error(w, msg, http.StatusMethodNotAllowed)
return nil, errors.New(msg)
}
contentType := r.Header.Get("Content-Type")
// TODO: do we assume contentType is lowercase? we did before
contentSubtype, validContentType := grpcutil.ContentSubtype(contentType)
if !validContentType {
msg := fmt.Sprintf("invalid gRPC request content-type %q", contentType)
http.Error(w, msg, http.StatusUnsupportedMediaType)
return nil, errors.New(msg)
}
if r.ProtoMajor != 2 {
msg := "gRPC requires HTTP/2"
http.Error(w, msg, http.StatusHTTPVersionNotSupported)
return nil, errors.New(msg)
}
if _, ok := w.(http.Flusher); !ok {
msg := "gRPC requires a ResponseWriter supporting http.Flusher"
http.Error(w, msg, http.StatusInternalServerError)
return nil, errors.New(msg)
}
var localAddr net.Addr
if la := r.Context().Value(http.LocalAddrContextKey); la != nil {
localAddr, _ = la.(net.Addr)
}
var authInfo credentials.AuthInfo
if r.TLS != nil {
authInfo = credentials.TLSInfo{State: *r.TLS, CommonAuthInfo: credentials.CommonAuthInfo{SecurityLevel: credentials.PrivacyAndIntegrity}}
}
p := peer.Peer{
Addr: strAddr(r.RemoteAddr),View on GitHub (pinned to 03255a9237)
Solutions
- Ensure the upstream hop to the gRPC server speaks HTTP/2 (h2 over TLS, or h2c for plaintext).
- Configure the reverse proxy to negotiate h2 with the backend (e.g. Traefik/Envoy/Nginx h2 upstream).
- For plaintext Go servers, use h2c via golang.org/x/net/http2/h2c.
- Use a gRPC-aware client that defaults to HTTP/2.
Example fix
// before: HTTP/1.1-only server
// srv := &http.Server{Addr: ":80", Handler: grpcServer}
// srv.ListenAndServe()
//
// after: h2c server so plaintext HTTP/2 works
// h2s := &http2.Server{}
// handler := h2c.NewHandler(grpcServer, h2s)
// srv := &http.Server{Addr: ":80", Handler: handler}
// srv.ListenAndServe() Defensive patterns
Strategy: validation
Validate before calling
// Reject non-h2 requests early, before the gRPC transport sees them.
func requireHTTP2(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.ProtoMajor != 2 {
http.Error(w, "gRPC requires HTTP/2", http.StatusHTTPVersionNotSupported)
return
}
next.ServeHTTP(w, r)
})
} Prevention
- Serve gRPC over h2 (TLS) or h2c (plaintext) using golang.org/x/net/http2/h2c.
- Configure reverse proxies to use an HTTP/2 upstream.
- Use gRPC client libraries, which negotiate HTTP/2 by default.
When it happens
Trigger: An HTTP/1.1 client (plain curl without --http2, an HTTP/1.1-only proxy, or a browser/proxy that failed h2 negotiation) reaches the gRPC handler transport.
Common situations: A reverse proxy terminating TLS and forwarding to the backend over HTTP/1.1 instead of h2/h2c; a load balancer without h2 support; plaintext HTTP/1.1 health probes; missing http2.Enable in Go's http.Server.
Related errors
- transport: timeout string is too short: %q
- transport: timeout string is too long: %q
- transport: timeout unit is not recognized: %q
- received %d-bytes data exceeding the limit %d bytes
- received an illegal stream id: %v. headers frame: %+v
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/45655b4eba47ac19.
Report an issue: GitHub.