Example: HTTP Client Set Timeouts
HTTP requests can hang. Set a timeout.
A timeout policy should define how long the entire operation may take and, when necessary, how long individual phases may wait.
Quick Example
Client.Timeout is an overall wall-clock limit. It covers connection establishment, redirects, response headers, and reading the response body.
For a simple request, this is often the right starting point.
If you return early without consuming the body, close it. When connection reuse matters, a bounded drain may help the transport reuse the connection, but reuse is best-effort.
Client.Timeout Is the Overall Deadline
The default http.Client has a Timeout of zero, meaning it has no overall request timeout.
That is dangerous when the remote server, network, or response body can stall indefinitely.
The timeout covers the whole operation:
The important detail is that the timer does not stop when Do returns a Response.
It remains active while the response body is being read.
That means this can still time out:
A server that sends response headers quickly but then stops sending the body can still hit Client.Timeout.
context.WithTimeout for One Operation
Use a request context when the timeout belongs to the individual operation rather than the client itself.
A request context controls the lifetime of the outgoing request, including obtaining a connection, sending the request, waiting for response headers, and reading the response body.
This makes it useful when the caller already has an operation deadline.
For example, a request handled inside a larger operation should normally inherit that operation's context rather than inventing an unrelated timeout.
Client.Timeout vs Request Context
They solve related problems, but they express different ownership.
A useful pattern is:
The shortest applicable deadline wins.
Why One Timeout Is Sometimes Not Enough
Client.Timeout prevents the entire request from running forever.
But it does not tell you which phase is consuming the time budget.
Consider:
The request technically has a timeout, but almost the entire budget was consumed establishing the connection.
Phase-specific limits let you express stronger requirements:
This gives you both:
- an upper bound for the complete operation
- sharper failure boundaries for individual phases
The values are examples, not universal defaults. Choose them from your service's latency budget and upstream behavior.
Connection Timeout
A connection can stall before HTTP has even started.
When using a custom Transport, configure the dialer:
Start with http.DefaultTransport.Clone() rather than constructing a new http.Transport from scratch.
The default transport contains more than just its dial configuration, including connection-pool and protocol settings. Cloning it lets you change the timeout policy without accidentally replacing the rest of the transport configuration.
The dial timeout applies to connection establishment.
It does not limit:
- TLS negotiation
- waiting for response headers
- reading the response body
Those are separate phases.
TLS Handshake Timeout
For HTTPS, the TCP connection may succeed while the TLS handshake stalls.
Configure a separate limit when you need one:
This is particularly useful when diagnosing or controlling slow connection establishment to HTTPS services.
Response Header Timeout
A server may accept the request but take a long time before sending response headers.
Use:
This timeout starts after the request has been fully written.
It does not include reading the response body.
That distinction matters:
A slow body therefore still requires an overall deadline or another application-level policy.
A Layered Client
When an HTTP client needs both an overall deadline and phase-specific limits, combine them:
The numbers above are illustrative.
The important structure is:
The overall timeout remains necessary because phase-specific timeouts do not cover every part of the request lifecycle.
For example, ResponseHeaderTimeout does not limit response-body reading.
Reuse the Client
http.Client is safe for concurrent use.
Create it once and reuse it:
Do not create a new client and transport for every request.
The connection pool belongs to the transport. Recreating transports prevents effective connection pooling and can create unnecessary connections and resource pressure.
The usual production pattern is:
Timeout During Body Reading
A timeout can happen after Do has already returned successfully.
The response may therefore be only partially read when the timeout occurs.
Do not treat partially received data as a successful response unless the application protocol explicitly permits it.
For a fixed-length or structured response, the operation should normally be considered failed if the body cannot be completely consumed and validated.
Always close the response body.
Timeout Errors
There are two useful questions:
- Was the operation canceled or did its context deadline expire?
- Did a network operation report a timeout?
For a request context:
For network-level timeout classification:
Preserve the original error when adding context:
Do not flatten timeout errors into strings. Callers may need to distinguish cancellation, deadline expiration, timeout, and other network failures.
Client.Timeout Is Not an Idle Timeout
This is an important distinction for streaming responses.
Client.Timeout is an overall deadline.
It does not mean:
"Fail if no bytes arrive for 5 seconds."
Suppose a server sends one small chunk every few seconds:
An overall timeout eventually terminates the request, but it does not provide an idle-period policy.
Likewise, ResponseHeaderTimeout stops being relevant once the headers have arrived.
For legitimately long-lived streams, do not use Client.Timeout as an idle timeout.
Streaming protocols may need their own application-level liveness or heartbeat rules.
Request Uploads Also Consume the Overall Timeout
The overall client timeout includes sending the request.
This matters for large uploads.
ResponseHeaderTimeout does not start until the request has been fully written.
So a slow upload can consume most of the overall timeout before the response-header timer even becomes relevant.
This is another reason to think in terms of an overall deadline plus phase-specific policies rather than one universal timer.
Do Not Build a Timeout With a Timer Goroutine
Avoid patterns such as:
A timer does not cancel an HTTP operation by itself.
Use the cancellation mechanisms provided by net/http:
or:
The context approach is usually preferable when the timeout belongs to a particular operation.
Timeouts and Retries
A timeout is not automatically a retryable failure.
Before retrying, ask:
- Was the operation safe to retry?
- Did the server possibly receive the request?
- Is there enough time left in the caller's deadline?
For GET requests, retrying selected transient failures may be reasonable.
For side-effecting requests such as POST, a timeout does not tell you whether the server processed the request before the client timed out.
Do not turn:
into an unbounded extension of the original operation.
Retries should remain inside the original deadline.
For APIs that support idempotency keys, use them when the operation semantics require safe retrying.
Common Mistakes
No Overall Timeout
The client has no overall timeout.
For outbound requests to untrusted or unreliable systems, this is usually an unsafe default.
Only Setting ResponseHeaderTimeout
This does not protect you from a response body that stalls after the headers arrive.
Only Setting a Dial Timeout
This protects connection establishment, not the rest of the request.
Creating a New Transport for Every Request
This defeats effective connection pooling and creates unnecessary connection-management overhead.
Customize and reuse a transport instead.
Treating Timeout as Success
A partial response is not a successful response merely because some bytes arrived before the timeout.
Validate the complete response according to the application protocol.
Retrying Every Timeout
A timeout does not prove that the server did not process the request.
Be especially careful with non-idempotent operations.
Rule of Thumb
A practical baseline for a normal API client is:
Do not add every timeout simply because it exists.
Add a timeout when you can explain which failure boundary it protects.
Key Takeaways
- HTTP requests can hang. Set an overall timeout.
Client.Timeoutcovers the entire request lifecycle, including response-body reads.- Use
context.WithTimeoutwhen the deadline belongs to one operation or its caller. Dialer.Timeout,TLSHandshakeTimeout, andResponseHeaderTimeoutprotect specific phases.ResponseHeaderTimeoutdoes not cover response-body reading.Client.Timeoutis an overall deadline, not an idle timeout for streaming responses.- Customize
http.DefaultTransport.Clone()instead of rebuilding a transport from an empty struct unless you intentionally want to define all transport settings yourself. - Reuse
http.Clientandhttp.Transport. - A timeout can leave a response body partially read; close it and treat incomplete data according to the application protocol.
- Classify timeout errors with
net.Error.Timeout()when appropriate, and preserve errors with%w. - Do not retry timeouts blindly, especially for side-effecting requests.
- Choose timeout values from actual latency budgets and upstream behavior, not arbitrary numbers.