Go 1.27 fixes HTTP/1 connection reuse for unread response bodies
Go 1.27 improves HTTP/1 client performance by automatically handling unread response bodies, enabling connection reuse without manual code changes.
The Go team has released version 1.27, introducing a significant performance improvement for HTTP/1 clients that fail to read response bodies completely. In benchmarks against index.golang.org, the new runtime completes 100 sequential GET requests in approximately 150 milliseconds, compared to 710 milliseconds in Go 1.26. This fivefold speedup addresses a long-standing inefficiency where unused response data prevented TCP connection reuse.
What happened
Developers building backend services in Go have long encountered silent performance penalties when using the standard net/http package with HTTP/1. The issue arises when a client makes a request but does not fully consume the response body before closing it. In Go 1.26 and earlier, the HTTP client would detect the unread data and abandon the underlying TCP connection rather than attempting to reuse it. This forced the creation of a new connection for every subsequent request, incurring the full cost of TCP and TLS handshakes each time.
The improvement in Go 1.27 changes this behavior. The runtime now automatically drains unread response bodies when resp.Body.Close() is called, allowing the connection to remain alive and available for future requests. This change specifically targets HTTP/1 traffic, which is still widely used despite the prevalence of HTTP/2. By eliminating the need for developers to manually drain bodies, the update reduces latency and resource consumption for services that only check status codes or parse partial JSON responses.
How it works
To understand the fix, one must look at how HTTP/1 handles data transmission. Unlike HTTP/2, which multiplexes multiple streams over a single connection, HTTP/1 sends responses sequentially as a continuous stream of bytes. The client reads the status line and headers first, then the body. For the connection to be reused, the client must read every byte of the current response body so it knows exactly where the next response begins. If any bytes remain unread, the client cannot safely determine the start of the next message, leading to protocol errors if the connection is reused.
In previous versions, if a developer called resp.Body.Close() without reading the entire body, the net/http package would mark the connection as unusable. It would then close the socket and open a new one for the next request. This process involves a TCP handshake, which takes one round-trip time (RTT), and a TLS handshake, which can take two RTTs for TLS 1.2 or one for TLS 1.3. In high-latency networks, these handshakes dominate the total request time.
Go 1.27 modifies the Close() method to automatically read and discard any remaining data in the response body buffer before releasing the connection back to the pool. This ensures the stream is positioned correctly for the next request. The runtime handles this efficiently, avoiding unnecessary blocking while ensuring protocol correctness. This mechanism mirrors what many experienced Go developers previously implemented manually by copying the body to io.Discard.
Key details
- Go 1.27 reduces the time for 100 sequential HTTP/1 GET requests from ~710ms to ~150ms in benchmark tests.
- The improvement applies specifically to HTTP/1 connections; HTTP/2 was not affected by this issue due to its multiplexing architecture.
- Previous workarounds required developers to manually drain response bodies using
io.Copy(io.Discard, resp.Body)before closing. - The fix automatically consumes unread bytes when
resp.Body.Close()is invoked, preserving connection reusability. - Connection reuse failures often occurred in code that only checked
resp.StatusCodeor parsed JSON without reading trailing newlines. - The change is backward compatible and requires no code modifications for existing applications to benefit.
Why it matters
For software engineers building microservices or API clients, this update removes a common source of unexpected latency. Many applications only care about the success or failure of an API call, indicated by the HTTP status code, and ignore the response body. Without this fix, such applications would suffer from poor performance due to constant connection churn. The overhead of establishing new TCP and TLS connections is significant, especially in distributed systems where services communicate across regions with higher network latency.
Furthermore, this change reduces the cognitive load on developers. Previously, teams had to enforce strict coding standards to ensure all response bodies were drained, often requiring custom wrapper functions or linter rules. Missed instances led to subtle performance degradation that was difficult to diagnose because the code appeared correct and functional. With Go 1.27, the standard library handles this edge case internally, making safe and efficient HTTP client usage the default behavior.
What you can do
- Upgrade your Go toolchain to version 1.27 to benefit from automatic connection reuse improvements.
- Review existing HTTP client code to identify places where response bodies are ignored or partially read.
- Remove manual body draining logic such as
io.Copy(io.Discard, resp.Body)if it was added solely for connection reuse. - Test your service’s latency under load to observe the reduction in connection establishment overhead.
- Ensure your monitoring tools track connection reuse rates to verify the improvement in production environments.
- Continue to use HTTP/2 where possible for inherent multiplexing benefits, but rely on this fix for legacy HTTP/1 endpoints.



