# How to Fix Go HTTP Client Timeout

HTTP client timeout errors occur when requests take longer than the configured timeout. Proper timeout configuration is essential for robust HTTP clients.

Error Patterns

Context Deadline Exceeded

text
context deadline exceeded
Get "https://example.com/api": context deadline exceeded

Connection Timeout

text
net/http: request canceled while waiting for connection
dial tcp 192.0.2.1:80: i/o timeout

Response Header Timeout

text
net/http: timeout awaiting response headers

TLS Handshake Timeout

text
net/http: timeout awaiting TLS handshake

Default Behavior Problem

go
// WRONG: No timeout set
client := &http.Client{}  // Default has NO timeout!
resp, err := client.Get("https://slow-server.com")
// Can hang forever

Solutions

Solution 1: Set Client Timeout

```go import ( "net/http" "time" )

// Create client with timeout client := &http.Client{ Timeout: 30 * time.Second, // Total request timeout }

resp, err := client.Get("https://example.com") ```

Solution 2: Use Context with Timeout

```go import ( "context" "net/http" "time" )

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel()

req, err := http.NewRequestWithContext(ctx, "GET", "https://example.com", nil) if err != nil { return err }

resp, err := http.DefaultClient.Do(req) ```

Solution 3: Configure Individual Timeouts

go
client := &http.Client{
    Transport: &http.Transport{
        DialContext: (&net.Dialer{
            Timeout:   30 * time.Second,  // Connection establishment
            KeepAlive: 30 * time.Second,
        }).DialContext,
        TLSHandshakeTimeout:   10 * time.Second,  // TLS handshake
        ResponseHeaderTimeout: 10 * time.Second,  // Wait for response headers
        ExpectContinueTimeout: 1 * time.Second,
    },
    Timeout: 60 * time.Second,  // Total request timeout
}

Solution 4: Implement Retry with Backoff

```go import ( "context" "net/http" "time" )

func retryRequest(ctx context.Context, url string, maxRetries int) (*http.Response, error) { client := &http.Client{Timeout: 10 * time.Second}

var lastErr error for i := 0; i < maxRetries; i++ { reqCtx, cancel := context.WithTimeout(ctx, 10*time.Second) req, err := http.NewRequestWithContext(reqCtx, "GET", url, nil) if err != nil { cancel() return nil, err }

resp, err := client.Do(req) cancel()

if err == nil { return resp, nil }

lastErr = err

// Check if it's a timeout error if errors.Is(err, context.DeadlineExceeded) { // Wait before retry with exponential backoff backoff := time.Duration(i+1) * time.Second time.Sleep(backoff) continue }

// Non-retryable error return nil, err }

return nil, fmt.Errorf("after %d retries: %w", maxRetries, lastErr) } ```

Solution 5: Use Circuit Breaker Pattern

```go import ( "github.com/sony/gobreaker" )

var cb = gobreaker.NewCircuitBreaker(gobreaker.Settings{ Name: "HTTP Client", Timeout: 30 * time.Second, })

func requestWithCircuitBreaker(url string) (*http.Response, error) { result, err := cb.Execute(func() (interface{}, error) { client := &http.Client{Timeout: 10 * time.Second} return client.Get(url) })

if err != nil { return nil, err }

return result.(*http.Response), nil } ```

Solution 6: Handle Timeout Gracefully

```go func makeRequest(url string) (*http.Response, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel()

req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, err }

client := &http.Client{Timeout: 5 * time.Second} resp, err := client.Do(req)

if err != nil { // Check specific error types if errors.Is(err, context.DeadlineExceeded) { return nil, fmt.Errorf("request timed out: %w", err) } if errors.Is(err, context.Canceled) { return nil, fmt.Errorf("request was canceled: %w", err) } return nil, fmt.Errorf("request failed: %w", err) }

return resp, nil } ```

Solution 7: Timeout for Slow Response Bodies

```go func readBodyWithTimeout(resp *http.Response, timeout time.Duration) ([]byte, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel()

// Use a goroutine to read body done := make(chan struct{}) var body []byte var err error

go func() { body, err = io.ReadAll(resp.Body) close(done) }()

select { case <-done: return body, err case <-ctx.Done(): resp.Body.Close() return nil, ctx.Err() } } ```

Solution 8: Use httputil for Debugging

```go import "net/http/httputil"

func debugRequest(req *http.Request) { dump, err := httputil.DumpRequestOut(req, true) if err != nil { log.Fatal(err) } fmt.Printf("%s\n", dump) } ```

go
// Recommended configuration
client := &http.Client{
    Transport: &http.Transport{
        DialContext: (&net.Dialer{
            Timeout:   5 * time.Second,  // Connection timeout
            KeepAlive: 30 * time.Second,
        }).DialContext,
        TLSHandshakeTimeout:   5 * time.Second,
        ResponseHeaderTimeout: 10 * time.Second,
        IdleConnTimeout:       90 * time.Second,  // Keep-alive timeout
    },
    Timeout: 30 * time.Second,  // Total timeout
}

Timeout Guidelines

Timeout TypeRecommended ValueUse Case
Connection Dial5-10sEstablish TCP connection
TLS Handshake5-10sHTTPS connection
Response Header10-30sServer to start responding
Total Request30-60sEnd-to-end request
Idle Connection90sKeep connections alive

Testing Timeouts

```go // Create a slow server for testing func slowServer() *http.Server { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { time.Sleep(5 * time.Second) // Simulate slow response w.Write([]byte("done")) })

server := &http.Server{Addr: ":8080", Handler: handler} go server.ListenAndServe() return server }

func TestTimeout(t *testing.T) { server := slowServer() defer server.Close()

client := &http.Client{Timeout: 1 * time.Second}

_, err := client.Get("http://localhost:8080") if !errors.Is(err, context.DeadlineExceeded) { t.Errorf("expected timeout error, got: %v", err) } } ```

Prevention Tips

  1. 1.Always set a timeout - Never use default client without timeout
  2. 2.Use context for cancellation - Allow caller to cancel requests
  3. 3.Implement retry logic - Handle transient failures
  4. 4.Use appropriate timeout values - Balance between user experience and reliability
  5. 5.Monitor timeout rates - Track timeouts in production
  • connection refused - Server not running
  • no such host - DNS resolution failure
  • too many open files - Resource exhaustion