Tracing & Performance

Connection vs Read Timeout: When & Why

Distinguish connection timeout vs read timeout in HTTP debugging. Use distributed traces to pinpoint which service is failing and why.

Your API returns a 504 error after exactly 30 seconds. You're staring at the stack trace wondering: did the request never leave your server, or did it hang mid-way to the destination? That 30-second moment defines the difference between a connection timeout and a read timeout—and it's the first clue to solving why your downstream service isn't responding. Understanding the distinction isn't academic; it changes your debugging strategy, your configuration, and which service you should actually be investigating.

A connection timeout fires when your code fails to establish a TCP socket to the destination within the timeout window. A read timeout fires after the connection succeeds, but the response never arrives (or arrives too slowly). Both look like failures from the caller's perspective, but they point to different root causes. When you're running distributed systems with distributed tracing, the span timeline shows you exactly which timeout occurred and which service is at fault.

Connection Timeout: The Three-Way Handshake Never Completes

A connection timeout is the failure to establish a TCP three-way handshake (SYN, SYN-ACK, ACK) before the timeout expires. This typically means:

  • The destination host is down or unreachable
  • A firewall is blocking or silently dropping packets
  • The DNS lookup failed or returned a bad address
  • The destination is overloaded and not accepting new connections
  • A network partition or congestion caused the handshake to exceed the timeout

When a connection timeout fires, no data has crossed the network. Your request was never accepted by the server. The failure is almost always upstream of the destination server itself—a networking layer issue, DNS problem, or the server not running.

If you're using a Java HTTP client, a connection timeout might look like this:

HttpRequest request = HttpRequest.newBuilder()
    .uri(new URI("https://api.downstream.com/data"))
    .timeout(Duration.ofMillis(3000))
    .GET()
    .build();

try {
    HttpResponse<String> response = httpClient
        .sendAsync(request, HttpResponse.BodyHandlers.ofString())
        .get();
} catch (TimeoutException e) {
    System.err.println("Request timed out: " + e.getMessage());
}

A more granular approach uses OkHttp or Apache HttpClient to set explicit connection vs. read timeouts:

OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(3, TimeUnit.SECONDS)
    .readTimeout(10, TimeUnit.SECONDS)
    .build();

Request request = new Request.Builder()
    .url("https://api.downstream.com/data")
    .build();

try {
    Response response = client.newCall(request).execute();
} catch (SocketTimeoutException e) {
    if (e.getMessage().contains("connect")) {
        System.err.println("Connection timeout");
    } else {
        System.err.println("Read timeout");
    }
}

Read Timeout: The Connection Worked, But Data Never Arrived

A read timeout fires after the TCP connection succeeds—the handshake completed, the request was sent—but the response fails to arrive within the timeout window. This happens when:

  • The destination server is processing but too slowly
  • The server crashed after accepting the connection
  • Network congestion between client and server is delaying the response
  • The server is stuck in an infinite loop or blocking operation
  • Back-end database or I/O is slow and the server is waiting

A read timeout means the server received your request, but something went wrong during its execution or transmission of the response. The failure is likely on the destination server or its dependencies (database, cache, third-party API it calls).

Many HTTP clients conflate connection and read timeouts into a single "timeout" parameter. Always check the client library's documentation to confirm it lets you set them separately.

Spotting the Difference in Traces and Logs

When a request times out, the question is: how do you know which one happened? If you're sending requests through distributed tracing, each span from the calling service records when it sent the request and when it gave up. By examining the span waterfalls, you can see:

  • Connection timeout: the span exits immediately (within milliseconds of the client initiating), no downstream span created
  • Read timeout: the span exists for the full timeout duration, then exits; the downstream service may or may not have created a span

LightTrace captures these timeouts in error spans and displays the exact duration. If your client waited 30 seconds before failing, and the downstream service's span only covers the first 2 seconds, you know the read happened but the response got stuck somewhere between the server and your client.

In logs, look for the exception type and message:

Java: SocketTimeoutException: Read timed out vs. java.net.ConnectException: Connection timed out

Node.js: Error: connect ETIMEDOUT vs. Error: socket hang up

Python: requests.exceptions.ConnectTimeout vs. requests.exceptions.ReadTimeout

Go: context deadline exceeded (does not distinguish, but paired with a low-level dial vs. read error, you can tell)

Configuration Best Practices

Connection timeout should typically be short (1–5 seconds). If the destination is in the same region or private network, 2–3 seconds is reasonable. If it crosses the internet, allow 5 seconds. Longer than that and you're just delaying the inevitable signal that the server is unreachable.

Read timeout should reflect your worst-case acceptable latency. For a background job, read timeout might be 30–60 seconds. For a user-facing API endpoint, it might be 10–15 seconds. Set it relative to your SLA, not relative to the connection timeout. If a database query can legitimately take 20 seconds under load, your read timeout must be at least 20 seconds.

OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(3, TimeUnit.SECONDS)
    .readTimeout(15, TimeUnit.SECONDS)
    .build();

Set your timeouts in configuration or environment variables, never hard-code them. When you onboard to a new cloud region or add a slower dependency, you'll need to adjust without redeploying.

Cascading Timeouts in Distributed Systems

In a microservices architecture, timeouts cascade. If Service A calls Service B with a 15-second timeout, and Service B calls Service C with a 15-second timeout, Service A effectively has only a few seconds to wait for B's call to C plus B's own processing. When combined with network jitter and retries, the chain collapses.

The pattern is: set timeouts to account for the depth of the call stack. If you're 3 layers deep (API → Service B → Service C → database), each hop should have progressively tighter timeouts. Service A might set 15 seconds, B sets 10 seconds, C sets 5 seconds. This ensures that C has room to wait for the database, B has room to process C's result, and A has room to process B's result.

When timeouts do fire in a distributed system, distributed tracing is your best debugging tool. The complete trace from entry point to timeout shows you exactly which service and which operation consumed the time.

Detecting Systematic Timeout Issues

If timeouts are happening regularly, not as isolated blips, the issue is systematic. Monitor your transaction latencies (p50, p75, p95, p99) over time. If p99 is regularly hitting your read timeout threshold, you need to either:

  1. Increase the timeout (if the service legitimately needs more time under load)
  2. Optimize the slow operation (database query, external API call, or internal loop)
  3. Add caching or async processing to avoid the blocking call altogether

LightTrace's slow-transaction detection alerts you when transaction latencies start trending upward, giving you a heads-up before timeouts start firing in production.

Increasing timeouts without addressing the root cause is a band-aid. If your read timeout keeps firing at 30 seconds, and you bump it to 60, you've only delayed the user-visible failure and made your system less responsive to actual outages.

The Debugging Workflow

When you encounter a timeout error:

  1. Check the error message or span duration. Did the request timeout immediately (connection) or after the full timeout window (read)?
  2. Look at the distributed trace. Is there a downstream span? If yes, read timeout. If no, likely connection.
  3. Check the destination service's logs. Did it receive the request? If yes, the issue is downstream processing or response transmission. If no, the connection timeout fired before the request arrived.
  4. Adjust timeout configuration only after you understand where the time was actually spent.

Start tracking errors in minutes

When timeouts fire in production, pinpointing whether it's a connection or read timeout is the first step to fixing it. LightTrace shows the full distributed trace with exact span durations so you can see where each request waited. Start free and get clarity on your timeouts.

Connection timeouts and read timeouts are distinct signals. Learning to tell them apart—and configuring them separately—turns timeout errors from a mystery into a clear diagnostic.

Fix your next production error faster

Point any Sentry SDK at LightTrace — free up to 5,000 events/month.