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

  1. Ensure the upstream hop to the gRPC server speaks HTTP/2 (h2 over TLS, or h2c for plaintext).
  2. Configure the reverse proxy to negotiate h2 with the backend (e.g. Traefik/Envoy/Nginx h2 upstream).
  3. For plaintext Go servers, use h2c via golang.org/x/net/http2/h2c.
  4. 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

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


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/45655b4eba47ac19. Report an issue: GitHub.