Using HTTP/2 Cleartext for a server in Go 1.24

Hacker News Top Tools

Summary

Go 1.24 introduces native support for HTTP/2 cleartext (h2c) in the net/http package, eliminating the previous need for external wrapper packages. This post explains how to configure a Go HTTP server for h2c to work with services like Google Cloud Run.

No content available
Original Article
View Cached Full Text

Cached at: 05/24/26, 09:42 PM

# Configuring a Go HTTP Server for Unencrypted HTTP/2 (h2c) | ClarityBoss Blog Source: [https://www.clarityboss.com/blog/go-http2-cleartext-h2c-cloud-run](https://www.clarityboss.com/blog/go-http2-cleartext-h2c-cloud-run) In our application, we use long\-lived server\-sent event streams \(SSE\)\. These are set up to have a really long timeout and lifetime \- 15 minutes in our setup\. However, Google Cloud Run has a[known issue](https://docs.cloud.google.com/run/docs/troubleshooting#client-disconnect)in that client disconnects are not propagated to Cloud Run when using HTTP/1\.1 to communicate with the backend service\. Thus, I started looking into[using HTTP/2 for services](https://docs.cloud.google.com/run/docs/configuring/http2)\. Cloud Run terminates TLS at the frontend, but can forward traffic as either HTTP/1\.1 or HTTP/2 cleartext \(`h2c`\) traffic\. Normally, HTTP/2 always uses TLS, but HTTP clients and servers can often be configured to use a cleartext version of the protocol\. Cloud Run also makes you select the protocol to be used\. This is basically the “HTTP/2 with Prior Knowledge” setup noted in RFC 9113, section 3\.3\. ## Configuring a Go server for HTTP/2 cleartext Our first cut of`h2c`support predated the Go 1\.24 changes I will go into more detail below\. Most posts and guides on the internet will point you toward the old outdated approach, but I’ve included it below so it is easier to see the before and after, and migrate your own code if you need to\. ### Before Go 1\.24 \(old approach\) To use`h2c`, you had to use the`golang\.org/x/net/http`package, and go through a convoluted setup\. ``` import ( "net/http" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" ) handler := ... // the existing `handler` must be wrapped, attached to server, then configured h2s := &http2.Server{} handler = h2c.NewHandler(handler, h2s) srv := &http.Server{ Addr: fmt.Sprintf("%s:%d", "", 9888), Handler: handler, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 35 * time.Second, // https://cloud.google.com/load-balancing/docs/https/request-distribution#timeout-bes IdleTimeout: 620 * time.Second, } err = http2.ConfigureServer(srv, h2s) if err != nil { ... } ``` ### Go 1\.24\+ \(new approach\) With Go 1\.24, no`x/net/http2/h2c`wrapper is needed anymore, and the setup is a lot more readable\. You can configure protocols directly on`http\.Server`\. Reference:[Go 1\.24 net/http release notes](https://go.dev/doc/go1.24#nethttppkgnethttp)\. ``` handler := ... srv := &http.Server{ Addr: fmt.Sprintf("%s:%d", "", 9888), Handler: handler, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 35 * time.Second, // https://cloud.google.com/load-balancing/docs/https/request-distribution#timeout-bes IdleTimeout: 620 * time.Second, } srv.Protocols = new(http.Protocols) srv.Protocols.SetHTTP1(true) srv.Protocols.SetUnencryptedHTTP2(true) ``` ### Testing locally before deployment Testing is pretty simple\. This command will fail if HTTP/2 cleartext isn’t set up properly\. ``` curl -i --http2-prior-knowledge http://localhost:9888 ``` ## Terraform Cloud Run configuration Once your Go service can speak HTTP/2 cleartext, it needs to be configured in Cloud Run appropriately\. If you’re doing it via the Cloud Run console, follow the[HTTP/2 for services documentation](https://docs.cloud.google.com/run/docs/configuring/http2)\. We’re doing it via terraform \- this snippet below gives a general idea of the configuration\. ``` # Many settings have been omitted that are not relevant to this post. resource "google_cloud_run_v2_service" "api" { name = "api" location = var.primary_region invoker_iam_disabled = true ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER" template { containers { ... ports { name = "h2c" container_port = 9888 } } # Default is 80; with idle SSE connections, we can easily scale higher. max_instance_request_concurrency = 200 # Default is 300s, but with SSE, we want to allow long-lived connections. timeout = "900s" } lifecycle { ignore_changes = [client, client_version, template[0].revision] } } ``` No real changes were needed at the load balancer level, since HTTPS is used to communicate with Serverless NEGs, and it seems like it properly upgrades the connection to HTTP/2 during that negotiation\. Also, the default timeouts for serverless backends is 60 minutes, not 30 seconds like some other backends\.

Similar Articles

How I use HTMX with Go

Lobsters Hottest

This blog post by Alex Edwards explains how to integrate HTMX with Go for server-side rendered web applications, covering template structure, partial responses, redirects, and error handling.

Codex Discovered a Hidden HTTP/2 Bomb

Lobsters Hottest

Codex discovered a remote denial-of-service exploit dubbed 'HTTP/2 Bomb' that targets HPACK compression in major web servers (nginx, Apache, IIS, Envoy, Pingora), chaining a compression bomb with flow-control hold to exhaust server memory quickly.

Tracing HTTP Requests with Go's net/HTTP/httptrace

Hacker News Top

This article explains how to use Go's net/http/httptrace package to trace HTTP request phases (DNS, connection, TLS, etc.) via context-based hooks, and demonstrates building a CLI trace tool and a RoundTripper logger.

Concurrent Servers: Part 8 - Go

Eli Bendersky

This article is part 8 of a series on writing concurrent network servers, focusing on implementing concurrency in Go using goroutines and the Go runtime's scheduling.

Adding Go to a browser code runner

Lobsters Hottest

The author details the challenges and solution for adding Go as a language to a browser-based code runner (dailyprog), explaining why the standard GOOS=js approach fails in a V8 isolate and how using GOOS=wasip1 with a minimal WASI host shim succeeds.