grpc/grpc-go · error

gRPC requires HTTP/2

Error message

gRPC requires HTTP/2

What it means

Returned by NewServerHandlerTransport when r.ProtoMajor is not 2. gRPC is built on HTTP/2 and cannot function over HTTP/1.1 because it relies on HTTP/2 multiplexing, streaming, and trailers. The transport rejects the request with HTTP 505 HTTP Version Not Supported. This only affects the handler-server (net/http integration) transport path.

Solutions

  1. Enable HTTP/2 on your net/http server: use TLS with ALPN ('h2') via http.ServeTLS, or enable h2c for cleartext via golang.org/x/net/http2/h2c.
  2. Ensure the TLS certificate and server config advertise h2 in NextProtos.
  3. For cleartext (no TLS), wrap the handler with h2c.NewHandler.
  4. Verify clients and proxies are HTTP/2 capable.

Example fix

// before: HTTP/1.1 only
http.ListenAndServe(":8080", grpcHandler)
// after: enable h2c for cleartext HTTP/2
import "golang.org/x/net/http2/h2c"
h2s := &http.Server{Handler: h2c.NewHandler(grpcHandler, &http2.Server{})}
h2s.ListenAndServe()

// or with TLS + ALPN:
http.ListenAndServeTLS(":443", "cert.pem", "key.pem", grpcHandler)
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the HTTP server supports HTTP/2 before serving gRPC.
// With TLS: use http.ServeTLS with ALPN h2.
// Without TLS: use h2c.NewHandler.
import "golang.org/x/net/http2/h2c"
h2s := &http.Server{
    Handler: h2c.NewHandler(grpcServer, &http2.Server{}),
}
h2s.ListenAndServe()

Try / catch

st, err := transport.NewServerHandlerTransport(w, r, stats, pool)
if err != nil {
    if strings.Contains(err.Error(), "requires HTTP/2") {
        log.Fatal("server must use HTTP/2; enable h2c or TLS+ALPN")
    }
}

Prevention

When it happens

Trigger: An HTTP/1.1 request reaches the gRPC handler transport. This happens when the net/http server is not configured for HTTP/2 (no TLS, or TLS without h2 ALPN, or h2c not enabled for cleartext), so the request arrives as HTTP/1.1.

Common situations: Running gRPC handler on plain HTTP without h2c; TLS server without ALPN negotiation for h2; using http.ListenAndServe (HTTP/1.1 only) instead of http2-compatible setup; client connecting via HTTP/1.1 proxy that doesn't upgrade.

Related errors


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

Appendix: source

Thrown at internal/transport/handler_server.go:71

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),
		LocalAddr: localAddr,

View on GitHub (pinned to 0c51461d27)