Fixing 'context deadline exceeded' in Go: HTTP and Database Timeouts

intermediate🔷 Go2026-04-23| Go (Golang) 1.13+, Linux, macOS, Windows

Error Message

context deadline exceeded
#go#golang#context#timeout#http#database

The Error Message

You’re likely here because your production logs just spiked with a cryptic message. In Go, this error is the runtime’s way of saying, "You told me to wait X amount of time, but the operation took X + 1." It usually surfaces in your logs like this:

Get "https://api.payments.com/v1/charge": context deadline exceeded

Or, if a database query hangs during a heavy migration:

panic: context deadline exceeded

Root Cause: Why does this happen?

The context deadline exceeded error triggers when a context.Context hits its timer before the task finishes. Go uses Context to prevent "zombie" processes from eating up your CPU and memory when an upstream service stalls.

Think of it as a protective kill-switch. Common culprits include:

  • External API Latency: A third-party service is having a bad day, pushing p99 response times from 200ms to 10 seconds.
  • Unindexed DB Queries: A SELECT statement on a table with 5 million rows is performing a full table scan.
  • Aggressive Timeouts: Setting a 50ms timeout for a complex TLS handshake that naturally requires 150ms.
  • Cold Starts: Serverless functions (like AWS Lambda) taking 3 seconds to spin up while your caller only waits for 1.

Fix 1: Configuring Realistic HTTP Client Timeouts

Standard library defaults are often dangerous. For example, http.DefaultClient has no timeout. If a remote server accepts a connection but never sends data, your goroutine will hang forever, eventually leading to a memory leak.

The Risk: Too Strict

// 10ms is rarely enough for a round-trip over the public internet
ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Millisecond)
defer cancel()

req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.github.com", nil)
res, err := http.DefaultClient.Do(req) // Guaranteed failure in most environments

The Robust Approach

Define a custom client with a global guardrail, then use context for per-request granularity.

client := &http.Client{
    Timeout: time.Second * 30, // The absolute ceiling
}

// Give this specific request 5 seconds to finish
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()

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

resp, err := client.Do(req)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        fmt.Println("The upstream API failed to respond within 5 seconds.")
    }
    return
}

Fix 2: Optimizing Database Query Windows

Database drivers like pgx or database/sql respect context. If you see this error in your DB layer, your query is likely competing for locks or processing too much data.

Example with SQL Context

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

// QueryContext ensures we don't wait forever for a locked row
query := "SELECT email FROM users WHERE last_login < $1"
rows, err := db.QueryContext(ctx, query, "2023-01-01")
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        log.Println("Query aborted: Execution exceeded 3-second limit. Check indexes on 'last_login'.")
    }
    return err
}

Pro-tip: In web handlers, use r.Context(). If a user refreshes their browser, the context cancels automatically. This stops your database from wasting cycles on a result the user will never see.

Fix 3: Validating the Environment

If your code looks correct but the error persists, the bottleneck is external. Use these steps to isolate the lag:

  • Increase the Limit: Temporarily bump the timeout to 60s. If the task finishes in 45s, your code is fine—your downstream service is simply slow.
  • Measure Latency: Run curl -o /dev/null -s -w "Connect: %{time_connect} TTFB: %{time_starttransfer} Total: %{time_total}\n" https://api.url.
  • Analyze SQL: Run EXPLAIN ANALYZE on the failing query to find missing indexes or sequential scans.

Verification Steps

  • Simulate Latency: Use a tool like Toxiproxy to add 5 seconds of lag to your local environment and verify your error handling triggers.
  • Check Error Wrapping: Always use errors.Is(err, context.DeadlineExceeded) rather than string matching.
  • Audit Logs: Ensure your logs capture which URL or query timed out. A generic "deadline exceeded" message is useless without context.

Best Practices

  1. Avoid Hardcoding: Store durations in your config file (e.g., API_TIMEOUT=5s) so you can tune performance without a re-deploy.

  2. Middleware Guardrails: Use a timeout middleware in Gin or Echo to set a global 10-second limit on all incoming requests. This prevents a single slow endpoint from taking down your entire service.

// Example: Setting a global 5-second limit for all routes
router.Use(timeout.New(
    timeout.WithTimeout(5 * time.Second),
    timeout.WithHandler(func(c *gin.Context) {
        c.Next()
    }),
))

Related Error Notes