Go 1.27 to Improve HTTP/1 Connection Reuse
In Go 1.27, net/http.Transport will asynchronously drain unread HTTP/1 response bodies, up to 256 KB or 50 ms. This should help reuse connections more often without extra code.
In Go 1.27, net/http.Transport will automatically handle unread HTTP/1 response bodies so it can keep connections available for subsequent requests more often. As Nicolas Houssin and Damien Neil explain in the Go blog post dated October 14, 2026, the change applies when a program closes resp.Body without reading the response to the end. Developers only need to close resp.Body when they no longer need the body.
Why Closing the Response Wasn't Enough
In HTTP/1, responses on a connection arrive sequentially in a shared byte stream. The net/http package parses the status line and headers before returning control to the program, but the body remains in the stream for the calling code to read through resp.Body. Until the body has been read to the end, the client cannot tell where the next response on that connection begins.
This is easy to miss when a program only needs the status. The server may send additional details in the response body. The code closes resp.Body, but in Go 1.26 and earlier, net/http discards the connection if any of the body remains unread. The next request then needs a new connection.
Even parsing the body does not always solve the problem. encoding/json.Decoder stops reading after the closing brace of a JSON object. If the server sends a newline after it, that single unread byte is enough to prevent the connection from being reused. So checking whether Close is called does not tell you whether the client is losing connections.
Establishing a new HTTPS connection requires network round trips: first for TCP, then for TLS. In the authors' TLS 1.2 example, three RTTs—round-trip times—pass before an HTTP request can be sent. That overhead matters even when the response itself is small. HTTP/2 works differently: requests use separate streams within a single connection, so an unread response body does not prevent other requests from being sent.
How Transport Will Free Up Connections
Starting with Go 1.27, net/http.Transport will asynchronously drain unread HTTP/1 response bodies, subject to limits of 256 KB and 50 ms. If the remaining data arrives within those limits, the connection can be reused. If the body is large or the data arrives slowly, Transport will not keep reading without limit just to preserve the connection.
Because the draining happens asynchronously, calling code does not have to wait for the server to send the rest of the body. The next request may open another connection, while the connection freed later can be used for subsequent requests. If the response includes a Connection: close header, Transport does not try to drain the body: that connection cannot be kept anyway. The authors caution that the details of this behavior may change in future releases.
Previously, developers handled this manually by copying the rest of the body to io.Discard and then closing resp.Body. But doing so without size or time limits is not always sensible. Downloading a large response to reuse a connection may take longer than establishing a new one, while a slow transfer can hold up the program. Adding limits and background processing made the code more complex to address a problem developers might not notice right away.
What This Means in Practice
In the authors' example, a function requests the status from index.golang.org 100 times without reading the response bodies. With HTTP/1 forced via GODEBUG=http2client=0, it took about 710 ms with Go 1.26 and about 150 ms with Go 1.27 on one author's machine. That's a nearly fivefold difference in this particular test, not an estimate of how much faster any application will run.
Developers no longer need to add their own draining logic just for this case: when the response is no longer needed, they should close resp.Body. How noticeable the effect is depends on how often an HTTP/1 client leaves a body unread and then makes further requests. If that does not happen, the example's results do not apply to the service's workload.
My Take
It's good that this no longer calls for custom goroutines and timers. The rule for calling code stays simple: close the body when it is no longer needed. Transport handles the limits on background draining.
I'd measure the effect of upgrading against a particular service's requests rather than applying the example's numbers to them. First, you need to find out whether its HTTP/1 client ever leaves part of a response unread.
Sources
Where the news comes from. The text is a retelling in the author’s own words; the facts come from the source, the opinion is the author’s.
Full-stack developer
I build web services and maintain several projects on different CMSs. I prefer simple architectures that don't need a separate handover manual.
All posts by the authorRelated articles
A Firefox Setting Helped the Author Resume Large Downloads
The author found that changing network.http.response.timeout helped him resume interrupted large downloads in Firefox. It’s an experimental tip, and resuming also depends on server support.
Cloudflare Introduces Origin Range Requests to Cache Parts of Large Files
Cloudflare has introduced Origin Range Requests to cache parts of large files. Available on all plans, the feature roughly halved R2’s p99 time to first byte after the migration, according to the company.
Brotli Decoder Vulnerability Poses Remote Code Execution Risk
A vulnerability in Brotli’s C decoder could lead to remote code execution. Developers should check their Node.js versions, Python libraries, and embedded copies of the decoder.
Discussion 4
Otabek Hamraev
does the 256 kb / 50 ms limit mean transport may still give up and discard the connection if the unread body is larger or slower to drain? curious where that threshold lands in real apps, because “just close it” used to be a tiny footgun and now it comes with a timer 😅
Timo Zimmermann
Yes—those are best-effort limits: if draining hits 256 KB or 50 ms, Transport can abandon that connection rather than hold it up, so large or slow bodies may still prevent reuse. The timer is an internal bound on cleanup, not a delay your code needs to manage; for bodies you need to consume, read them as usual before closing.
Mark T. Post author
Correct: if the body exceeds 256 KB or cannot be drained within 50 ms, the transport may discard the connection; the limits bound cleanup work, not guarantee reuse.
Andrii Lytvyn
One practical detail worth checking is whether the body was fully consumed before `Close`—a deferred close alone won’t help if the code returns after reading only a small prefix. It’d be useful to log when reuse is lost alongside bytes read, since the 256 KB and 50 ms cutoffs can otherwise look like intermittent connection churn.