LmCast :: Stay tuned in

Using HTTP/2 Cleartext for a server in Go 1.24

Recorded: May 24, 2026, 8:58 p.m.

Original Summarized

Configuring a Go HTTP Server for Unencrypted HTTP/2 (h2c) | ClarityBossClarityBossCBHomeBlogConfiguring a Go HTTP Server for Unencrypted HTTP/2 (h2c)Configuring a Go HTTP Server for Unencrypted HTTP/2 (h2c)Go 1.24 vastly simplified the configuration of a server that supports HTTP/2 cleartext, no longer requiring non-stdlib packages.I'll give a brief outline of how to enable HTTP/2 cleartext (h2c) in a Go API server when you are using Go 1.24 or newer. This is useful in Cloud Run environments, which handles TLS termination but can take advantage of HTTP/2 features.Dan McGee•May 18, 2026EngineeringIn 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 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.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 cleartextOur 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.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 deploymentTesting 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 configurationOnce 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. 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.ClarityBossGet Results. Keep Your People.Get the tool built for managers.Click Here to Try It!Back to BlogTrack one on ones, deliver feedback, and build trust with your team. Expertise lives here.ClarityBossFAQIntegrationsPricingBlogSign InSolutionsPhysical TherapyCompanyAbout UsResources LinkedInYouTubePoliciesTerms & ConditionsPrivacy PolicySecurity PolicyData Processing AgreementContactContact UsSupport© 2026 Entalas. All rights reserved. ClarityBoss is a product of Entalas.

The configuration of a Go HTTP server to support unencrypted HTTP/2 cleartext, often referred to as h2c, has evolved significantly with updates to the Go language itself, providing a more streamlined approach for developers. This capability is particularly relevant in environments like Google Cloud Run where TLS termination is handled at the frontend, yet the backend service can benefit from the features of HTTP/2, especially when managing long-lived streams such as Server-Sent Events or SSE, where traditional HTTP/1.1 client disconnect propagation is problematic.

Prior to Go version 1.24, enabling h2c support required a more complex setup. Developers had to utilize non-stdlib packages, specifically the golang.org/x/net/http2 and golang.org/x/net/http2/h2c, involving wrapping existing handlers and configuring the http2.Server explicitly through functions like http2.ConfigureServer. This older methodology involved a convoluted process that complicated the integration of standard server settings.

With the release of Go 1.24, this process was drastically simplified. The language update integrated native support for configuring protocols directly on the http.Server structure, eliminating the need for external wrappers. The modernized approach allows for the direct setting of protocols on the server instance by configuring the server's protocols object. Specifically, developers can set whether HTTP/1 is enabled and configure the server to set the unencrypted HTTP/2 protocol using the http.Server.Protocols interface.

When implementing this feature, the server configuration must also account for various timeouts, such as ReadHeaderTimeout, ReadTimeout, WriteTimeout, and IdleTimeout, which are crucial when dealing with long-lived connections, such as those required by SSE streams. Testing the successful setup locally involves using client tools, such as curl, with the appropriate flag to signal HTTP/2 prior knowledge, ensuring the server is correctly configured for the protocol negotiation.

Beyond the server-side implementation, deploying such a service in a platform like Cloud Run requires appropriate container configuration. For services utilizing HTTP/2 cleartext, properly configuring the service environment is necessary to allow the protocol to be utilized. The configuration in Cloud Run involves mapping the necessary ports and adjusting instance settings, such as request concurrency and timeouts, to accommodate the characteristics of the long-lived connections. It is worth noting that the underlying load balancer configuration for HTTPS traffic with Serverless Network Endpoints generally manages the HTTP/2 upgrade negotiation seamlessly, which further simplifies the deployment architecture, as established default timeouts for serverless backends are often more generous than those in traditional setups. This progression allows modern Go applications to leverage advanced protocol features efficiently in cloud-native environments.