Golang HTTP client timeout and server timeouts with a test server

Golang http client timeout and go http client timeout: http.Client zero means no timeout per pkg.go.dev, Transport dial TLS ResponseHeaderTimeout, context request deadline, golang http timeout vs golang http request timeout, golang http server timeout ReadHeader Read Write Idle, and client.timeout exceeded while awaiting headers with httptest examples.

Published

Updated

Read time 5 min read

Reviewed byDeepak Prasad

Golang HTTP client timeout and server timeouts with a test server

People searching golang http client timeout, go http client timeout, golang http.client timeout, golang http timeout, golang http request timeout, golang http client default timeout, go http client default timeout, or pkg.go.dev net http client timeout zero means no timeout are usually trying to cap how long an outbound call can hang. Searches for golang http server timeout or client.timeout exceeded while awaiting headers golang sit on the other side: server deadlines and the exact client error when headers never arrive. Popular guides such as Ilija Eftimov’s server timeouts article stress layering client, Transport, context, and http.Server fields instead of relying on defaults—http.DefaultClient keeps Timeout == 0, which means no timeout until you set one.

Below, every client example hits the same dummy HTTP server built with httptest so you can go run one file with no separate terminal or Postman. For broader HTTP background, see HTTP in Go.

Tested with Go 1.24 on Linux.


Dummy backend with httptest

httptest.NewServer starts a real net/http.Server on a loopback port. The mux used in the full program exposes:

  • GET /late-headers — sleeps three seconds before sending status or body (good for Client.Timeout and ResponseHeaderTimeout).
  • GET /late-body — sends 200 immediately, then sleeps three seconds before writing the body (headers arrive on time; body read can still hit Client.Timeout).

That split mirrors how awaiting response headers differs from reading the response body.


Client-side timeouts

http.Client.Timeout (golang http client timeout)

The Timeout field limits the entire call: dial (if needed), TLS, redirects, receiving headers, and reading the response body. The documentation states that a zero value means no timeout—that is the go http client default timeout behavior for http.DefaultClient.

If the server delays response headers past Timeout, you typically see:

text
Get "http://127.0.0.1:…/late-headers": context deadline exceeded (Client.Timeout exceeded while awaiting headers)

If headers return quickly but the body is slow, cancellation can surface while reading the body (often wrapped as a timeout-style error mentioning Client.Timeout).

http.Transport (finer-grained golang http timeout)

http.Transport controls the connection pool and per-phase deadlines, including:

Field Role
DialContext / net.Dialer.Timeout Cap TCP connect setup
TLSHandshakeTimeout Cap TLS negotiation
ResponseHeaderTimeout Cap wait for response headers after the request (including body) is fully written
ExpectContinueTimeout Wait for 100 Continue when using Expect: 100-continue
IdleConnTimeout How long an idle keep-alive connection may sit in the pool (not the same as end-to-end request time)

When ResponseHeaderTimeout fires first, errors look like:

text
Get "http://127.0.0.1:…/late-headers": net/http: timeout awaiting response headers

Prefer DialContext over the deprecated Transport.Dial field.

context.Context on the request (golang http request timeout)

http.NewRequestWithContext ties a single request to a deadline or cancellation. This is ideal when different RPCs need different budgets while sharing one http.Client. A typical error is plain context deadline exceeded.


Server-side timeouts (http.Server)

For production http.Server values, set explicit limits—common combinations appear in production writeups such as Eftimov’s resilient server timeouts guide:

Field Role
ReadHeaderTimeout Time allowed to read request headers (mitigates slow clients)
ReadTimeout Time allowed to read the entire request (headers + body)
WriteTimeout Time allowed to write the response
IdleTimeout Idle time before closing a keep-alive connection

Zero means no limit for each field. httptest.NewUnstartedServer returns a server whose Config you can tweak before Start()—handy for reproducing short WriteTimeout behavior without binding a real port yourself.

http.TimeoutHandler adds a handler-level wall clock; it still needs enough server WriteTimeout budget to emit its timeout response.


Complete example: one file, all scenarios

Save as main.go, run go run . from a module (go mod init example.com/timeouts). The program builds the dummy server, then exercises client timeout, transport header timeout, context deadline, slow-body client timeout, and a tight server WriteTimeout (client may see EOF when the connection is closed mid-response).

go
package main

import (
	"context"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"time"
)

func newTestServer() *httptest.Server {
	mux := http.NewServeMux()
	mux.HandleFunc("/late-headers", func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(3 * time.Second)
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("ok"))
	})
	mux.HandleFunc("/late-body", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		if f, ok := w.(http.Flusher); ok {
			f.Flush()
		}
		time.Sleep(3 * time.Second)
		_, _ = w.Write([]byte("body"))
	})
	return httptest.NewServer(mux)
}

func main() {
	srv := newTestServer()
	defer srv.Close()
	base := srv.URL

	fmt.Println("=== 1) http.Client.Timeout (headers late) ===")
	c1 := &http.Client{Timeout: 2 * time.Second}
	_, err := c1.Get(base + "/late-headers")
	fmt.Println(err)

	fmt.Println("\n=== 2) Transport.ResponseHeaderTimeout (headers late) ===")
	tr := &http.Transport{
		DialContext:           (&net.Dialer{Timeout: 3 * time.Second}).DialContext,
		TLSHandshakeTimeout:   5 * time.Second,
		ResponseHeaderTimeout: 500 * time.Millisecond,
	}
	c2 := &http.Client{Transport: tr}
	_, err = c2.Get(base + "/late-headers")
	fmt.Println(err)

	fmt.Println("\n=== 3) context.WithTimeout on request ===")
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()
	req, _ := http.NewRequestWithContext(ctx, http.MethodGet, base+"/late-headers", nil)
	_, err = http.DefaultClient.Do(req)
	fmt.Println(err)

	fmt.Println("\n=== 4) http.Client.Timeout while reading slow body ===")
	c3 := &http.Client{Timeout: 1 * time.Second}
	resp, err := c3.Get(base + "/late-body")
	if err != nil {
		fmt.Println(err)
	} else {
		_, err = io.ReadAll(resp.Body)
		resp.Body.Close()
		fmt.Println(err)
	}

	fmt.Println("\n=== 5) http.Server WriteTimeout via httptest ===")
	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(300 * time.Millisecond)
		_, _ = w.Write([]byte("hello"))
	})
	us := httptest.NewUnstartedServer(h)
	us.Config.WriteTimeout = 100 * time.Millisecond
	us.Start()
	defer us.Close()
	_, err = http.Get(us.URL)
	fmt.Println(err)
}

Example output shape from a Linux run (addresses and ports vary):

text
=== 1) http.Client.Timeout (headers late) ===
Get "http://127.0.0.1:…/late-headers": context deadline exceeded (Client.Timeout exceeded while awaiting headers)

=== 2) Transport.ResponseHeaderTimeout (headers late) ===
Get "http://127.0.0.1:…/late-headers": net/http: timeout awaiting response headers

=== 3) context.WithTimeout on request ===
Get "http://127.0.0.1:…/late-headers": context deadline exceeded

=== 4) http.Client.Timeout while reading slow body ===
context deadline exceeded (Client.Timeout or context cancellation while reading body)

=== 5) http.Server WriteTimeout via httptest ===
Get "http://127.0.0.1:…": EOF

Summary

golang http client timeout and go http client timeout are usually solved with http.Client.Timeout (remember zero means no timeout per pkg.go.dev), optionally combined with http.Transport knobs such as ResponseHeaderTimeout for header stalls, and context.WithTimeout for per-request budgets—those map to golang http timeout, golang http request timeout, and the client.timeout exceeded while awaiting headers golang message when headers never arrive in time. golang http server timeout is a separate layer: ReadHeaderTimeout, ReadTimeout, WriteTimeout, and IdleTimeout on http.Server, plus optional http.TimeoutHandler. The httptest dummy server above reproduces both sides without manual curl or a second binary.


References


Frequently Asked Questions

1. What does pkg.go.dev say about net http client timeout zero?

A zero http.Client.Timeout means no timeout; the timer still applies once set and covers dial, redirects, and reading Response.Body (see the Client struct documentation on pkg.go.dev).

2. Why do I see client.timeout exceeded while awaiting headers in golang?

That string is returned when http.Client.Timeout fires before response headers arrive; a short Transport.ResponseHeaderTimeout produces net/http: timeout awaiting response headers instead.

3. golang http.client timeout vs golang http request timeout via context?

Client.Timeout is one setting for the whole client; context.WithTimeout on a single http.Request scopes that call only and composes with cancellation and deadlines.

4. Should I set golang http server timeout fields?

Yes for public servers: at least ReadHeaderTimeout (slowloris mitigation), plus ReadTimeout, WriteTimeout, and IdleTimeout sized to your handlers; pair http.TimeoutHandler with enough WriteTimeout headroom if you wrap handlers.

5. go http client default timeout value?

http.DefaultClient uses a zero Timeout, which means no deadline—fine for quick scripts, risky for production outbound calls.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …