Example: HTTP Client Reuse Connections
Go's http.Transport caches connections and can reuse them for later requests.
The connection pool belongs to the Transport, not the http.Client. Reusing the same Transport avoids unnecessary TCP and TLS connection setup.
Quick Example
Create one client and reuse it:
The important part is that every request uses the same Transport:
Transport maintains the connection pool. http.Client uses that transport to make requests.
Don't Create a Transport Per Request
This creates a new connection pool for every request:
The transport has no opportunity to reuse connections created by a previous transport.
Instead, create the transport once:
The standard library explicitly recommends reusing transports. Transports are safe for concurrent use by multiple goroutines.
What About &http.Client{}?
This is different:
An http.Client with a nil Transport uses http.DefaultTransport.
So creating multiple empty http.Client values does not necessarily create multiple connection pools. They can all use the same default transport.
The important distinction is:
Reuse the
Transportwhen you need to control and preserve connection-pool state.
For application code, keeping the client and its transport together is usually simpler and makes the connection policy explicit.
Read and Close the Response Body
Close the response body even when reading it fails:
The defer belongs in a function whose lifetime matches one request.
Don't put it inside a large loop if that would keep many response bodies open until the outer function returns.
When practical, consume the response body before closing it. This gives the transport the opportunity to reuse the underlying connection.
Don't read an unbounded response just to improve connection reuse. Apply an appropriate response-size limit when the response size is not known.
Control Idle Connections
Transport provides several settings for controlling idle connections:
MaxIdleConns limits the total number of idle connections across all hosts.
MaxIdleConnsPerHost limits idle connections kept for one host.
IdleConnTimeout controls how long an idle connection remains in the pool before it is closed.
All three settings concern connections that are idle. They do not limit the number of active requests.
Watch the MaxIdleConnsPerHost Default
The default MaxIdleConnsPerHost is only 2.
That can be surprisingly small for a client making many concurrent HTTP/1.1 requests to the same service.
For example:
may be appropriate for a client that frequently talks to one upstream service.
Don't automatically set it to 100, though. The right value depends on request concurrency, traffic patterns, upstream limits, and whether the connection uses HTTP/1.1 or HTTP/2.
A low idle limit does not mean only that many requests can run concurrently. It determines how many completed connections are retained for future reuse.
Limit Active Connections
If the requirement is to limit the total number of connections to one host, use MaxConnsPerHost:
Unlike MaxIdleConnsPerHost, this includes connections that are:
- dialing
- active
- idle
When the limit is reached, additional connection attempts wait.
This makes MaxConnsPerHost useful when an upstream service has a connection limit or when you need to put a hard bound on connection concurrency.
HTTP/2 Uses Connections Differently
For HTTPS, Go's default transport supports HTTP/2 when the server and configuration allow it.
HTTP/2 can multiplex multiple requests over one connection.
As a result, connection counts and request concurrency are not interchangeable.
For example:
does not mean that only ten HTTP/2 requests can be in flight.
The limit is on connections, while HTTP/2 can carry multiple concurrent streams over a connection.
Reuse the Client Across Goroutines
A shared client is safe for concurrent use:
Multiple goroutines can use the same client:
Request-specific state belongs on the http.Request, not in the shared client.
Keep separate clients when different requests require different transport policies, such as different proxies, TLS configuration, or trust roots.
Set a Request Timeout
Connection pooling does not provide a request timeout.
An http.Client with the default Timeout has no overall request deadline.
For clients that need an application-wide upper bound, set one explicitly:
A request context can provide a more specific deadline when different operations need different limits:
The connection pool and timeout policy solve different problems. Keep both explicit.
When a Separate Transport Makes Sense
Separate transports are appropriate when connections must use different policies.
For example:
- different proxy settings
- different TLS configuration
- different client certificates
- different trusted CAs
Create those transports once and reuse each one.
Don't modify a shared Transport or its TLS configuration between requests to switch policies.
Close Idle Connections When Needed
A long-running application normally lets the transport manage its idle connections.
When the application needs to explicitly discard them, use:
This closes idle connections without interrupting connections that are currently in use.
This can be useful when shutting down a component or after changing the set of destinations it talks to.
Production Notes
Start with the default transport unless you have a measured reason to tune the pool.
For a high-concurrency client talking repeatedly to the same HTTP/1.1 service, check whether the default MaxIdleConnsPerHost of 2 is appropriate.
Don't assume a large MaxIdleConnsPerHost automatically improves performance. Idle connections consume resources, and the useful pool size depends on actual concurrency and traffic.
If you need to control the total number of connections to an upstream, use MaxConnsPerHost, not MaxIdleConnsPerHost.
And keep the fundamental rule simple:
Reuse the Transport.