Timeouts & deadlines  / field guide
Practitioner field guide · 2026-09-22

How long to wait: the number every framework gets wrong

A caller waits on a reply that may never come. Deciding how long to wait, and what to do when the wait runs out, looks like a one-line config value and is one of the most consequential choices in a distributed system. This guide reconstructs, from the public repository record, what the frameworks default to, why a single number cannot serve a whole call chain, and what production teams ship in its place.

25 primary sources 3 production incidents 5 rejected or stalled changes Evidence through Sept 2026 Read: 28 min
01

The territory

The problem is bounding an unbounded wait. A process asks another process for something and cannot know whether the answer is late, lost, or never coming. The timeout is the only tool it has to reclaim the thread, the connection and the attention it committed to that request, and the deadline is that same decision carried across a whole chain of calls.

Strip away the technology and the problem is old: you make a request to something you do not control, and you must decide, alone and in advance, how long to hold resources open for a reply that may never arrive. Hold too long and one slow dependency drains your connection pool, your threads, your memory, until you are down for a reason that has nothing to do with your own code. Give up too soon and you fail requests that would have succeeded, and if you retry them you can turn a slow dependency into an overloaded one. There is no safe default, because the right answer depends on latency you have not measured yet and on a call graph the framework author never saw.

What makes this a distributed-systems problem rather than a coding tip is that the decision does not stay local. A user-facing request fans out into a tree of downstream calls, and every node in that tree is separately deciding how long to wait. If the leaf waits longer than the root, the root gives up while the leaf keeps working, holding resources for an answer nobody will read. The unit that actually matters is not the per-call timeout but the deadline: one budget, set once at the edge, that every downstream call is measured against. gRPC, Finagle and MongoDB's drivers each converged on that idea independently, which is the first sign it is load-bearing rather than fashionable.

Two vocabulary distinctions run through everything below, and getting them straight is half the battle. A timeout is a duration measured from now (“wait 5 seconds”). A deadline is a point in time (“be done by 12:00:03”) that can be handed to a downstream service, which subtracts elapsed time and turns it back into a shorter timeout for its own calls. And an idle timeout (fires when no bytes move) is a different instrument from an overall timeout (fires when the whole operation takes too long); confusing them is how people accidentally cut off long streaming responses that were perfectly healthy.

154 min
Full GitLab.com outage from queries slowing ms→minutes and exhausting the pool, not from a missing timeout
15 s
Envoy's default route timeout, which cuts off gRPC streams that never chose it
8 yrs
Go's own “zero means infinity is a bad default” issue, still open in the backlog
5.0 s
httpx's default request timeout, the answer requests still refuses to ship

Figure 1 · The landscape: five instruments, four questions

A caller is waiting.
How long, and measured how?

Connect timeout
can I reach it at all?

Idle / read timeout
are bytes still moving?

Overall / request timeout
is the whole call taking too long?

Deadline
is the whole call TREE out of budget?

The only one that
composes across services

A caller is waiting.
How long, and measured how?

Connect timeout
can I reach it at all?

Idle / read timeout
are bytes still moving?

Overall / request timeout
is the whole call taking too long?

Deadline
is the whole call TREE out of budget?

The only one that
composes across services

Every “timeout” setting answers one of four different questions, and a single number cannot answer all four. Reconstructed from the Envoy timeout FAQ and the gRPC deadlines guide.
Diagram source

Scope. This guide is about the timeout and deadline decision itself: defaults, layering, propagation and the failures each produces. It touches retries and hedging only where they interact with deadlines. It deliberately does not cover circuit breakers, load shedding, idempotency, or the wider metastable-overload story, which the companion dig “The retry is the outage” already treats. And because the research ran against a network that reached the code-hosting and package sites but not most engineering blogs, its evidence is the repository record: source, issues, pull requests, specs, KEPs and GitLab's public incident tracker. Where the blog layer would normally add colour, this guide is thinner; where the repository record is unusually rich, it is thicker than most.

02

How it is actually built

Across HTTP clients, RPC frameworks, proxies, databases and orchestrators, the same three-layer shape recurs: a stack of independent timers, a mechanism to carry a shared budget across hops, and a policy for what happens when the budget runs out. The differences between systems are which layers they default on, and whether the budget is carried at all.

There is no single “the timeout.” Every mature system exposes a stack of them, each measuring a different segment of a request's life, and the reference architecture is that stack plus the wiring that keeps the segments consistent with one another. Envoy's own FAQ is the clearest catalogue: it documents a connection-level idle timeout defaulting to 1 hour, a stream idle timeout defaulting to 5 minutes, a request headers timeout that is disabled by default, and a route timeout that “defaults to 15 seconds” and is the one that bites [13]. A reader who sets one of these and assumes the others do not exist is the reader who files an incident.

Figure 2 · Reference architecture: the timeout stack on one request

Callee (also a caller)

On the wire

Caller

remaining budget

Connect timeout

Read / idle timeout

Overall request timeout

Deadline header
grpc-timeout / Finagle Deadline

Honours remaining deadline

Store: statement_timeout

Callee (also a caller)

On the wire

Caller

remaining budget

Connect timeout

Read / idle timeout

Overall request timeout

Deadline header
grpc-timeout / Finagle Deadline

Honours remaining deadline

Store: statement_timeout

The layers a single request passes through, each with its own timer. The deadline (dashed) is the only value that crosses service boundaries; everything else is local. Composite of the Envoy FAQ, the gRPC HTTP/2 protocol, and Finagle's Deadline context.
Diagram source

The layer that separates a resilient system from a fragile one is the middle box: whether the budget travels. gRPC defines it on the wire as a header, grpc-timeout, carrying a value and a unit, and specifies that “if Timeout is omitted a server should assume an infinite timeout” [16]. The deadlines guide explains the mechanism that makes it safe across machines with unsynchronised clocks: rather than shipping an absolute timestamp, “gRPC converts the deadline to a timeout from which the already elapsed time is already deducted” [17]. Each hop receives less budget than the last, which is exactly the property you want: the leaf of the call tree can never legitimately outlive its root.

Finagle reaches the same design from a different tradition. Its Deadline is a “broadcast Context that represents when the request should be completed by” [23], attached to the request and propagated automatically to every downstream call. The word “broadcast” is the tell: the deadline is not a parameter you remember to pass, it is ambient context that rides along whether or not the programmer thinks about it. That is the difference between a mechanism that works and one that works only when everyone remembers it, and it is the same reason gRPC makes propagation the default in Java and Go while leaving it opt-in in C++ [17].

The timer stack (local)

Connect, TLS, read/idle and overall timers, each bounding a different segment. Setting one and ignoring the rest is the most common mistake, because the unset ones keep their vendor defaults, which range from 15 seconds to infinity.

Catalogued at: Envoy, botocore

The budget carrier (distributed)

A deadline attached to the request and propagated hop by hop, decremented by elapsed time. This is the box most home-grown stacks are missing, and its absence is invisible until a deep call tree starts doing orphaned work.

Runs this way at: gRPC, Finagle

The expiry policy

What happens when the clock runs out: fail fast, retry within remaining budget, hedge a parallel copy, or cancel downstream work. The gRPC server “automatically cancelling a call” on deadline is a policy, not an accident.

Specified at: gRFC A6, MongoDB CSOT

The database sits at the bottom of every one of these stacks and plays by its own rules, which is why it deserves separate mention. PostgreSQL's statement_timeout defaults to zero, meaning disabled, and the manual actively warns against setting it globally: “Setting statement_timeout in postgresql.conf is not recommended because it would affect all sessions” [25]. The database's position is that it cannot know your application's intent, so it defaults to waiting forever and hands you the decision. That is defensible for a database and catastrophic as an application-wide philosophy, and the gap between those two readings is where GitLab spent three separate incidents, below.

03

The decisions that matter

Four forks recur across every system that got this right: what the default should be, whether one number or a budget governs a call chain, whether a proxy may trust a client's requested deadline, and whether to retry or hedge when the clock runs low. Each has a condition that flips the answer.

Decision: should the default timeout be “infinite” or a finite number?

Chosen (newer libraries)
  • Ship a finite default: httpx uses 5.0s [7], botocore 60s [24]
  • Rationale: a hang is a worse failure than a spurious timeout, and most callers never set one
Rejected / stalled (older cores)
  • requests, Go's http.DefaultClient and PostgreSQL default to no timeout [5][9][25]
  • Reason it lost / stalled: backward compatibility; a finite default silently breaks callers relying on long operations
Flips when
  • You control the deployment, not a library's whole ecosystem. An app or service should always set a finite timeout; only a general-purpose library that cannot know its callers' workloads has a real case for “no default”

The default fight is the most revealing, because it is being fought right now, in public, by the maintainers of the most-used libraries in the world, and they cannot agree. The requests library has debated a default timeout since 2016; issue #3070, “Consider making Timeout option required or have a default,” was closed and parked in a milestone literally named “Bankruptcy” [5]. Eight years later a maintainer opened PR #6709 to add 10-second connect and 30-second read defaults; as of this writing it is still open, its milestone bumped from 2.33.0 to 2.34.0 and then removed entirely [6]. The successor library, httpx, simply shipped DEFAULT_TIMEOUT_CONFIG = Timeout(timeout=5.0) and moved on [7]. Go's own Russ Cox filed “make default configs have better timeouts” in February 2018, noting that timeout fields treat “zero = infinity” and should instead have “a reasonable default”; it remains open in the Backlog milestone in 2026 [8]. The lesson for an architect is not which side is right. It is that if the library authors cannot settle this for their users, you cannot inherit a safe default from them, and the decision is yours whether you make it consciously or not.

Decision: govern a call chain with a per-call timeout, or one propagated deadline?

Chosen (RPC frameworks)
  • One deadline, set at the edge, propagated and decremented per hop [17][23]
  • Guarantees a leaf cannot outlive its root; bounds total work even across retries
Rejected (per-call timeouts)
  • Each service sets its own fixed timeout independently
  • Additive and uncoordinated: MongoDB found users “often unsure which timeout to use” among five-plus knobs [22]
Flips when
  • There is no call chain. A single leaf service with no downstream calls gains nothing from propagation and a plain per-operation timeout is simpler. The deadline earns its complexity as soon as depth ≥ 2

MongoDB's Client Side Operations Timeout spec is the cleanest statement of why the budget beats the knobs. It observes that users had “serverSelectionTimeoutMS, socketTimeoutMS, connectTimeoutMS, maxTimeMS, and wTimeoutMS” and that “because some of these timeouts are additive, it is difficult to set a combination which ensures control will be returned to the user after a specified amount of time” [22]. Their answer, a single timeoutMS governing the whole operation including retries, comes with a deliberate one-way door: “If timeoutMS is specified at any level, it cannot be later changed to unset at a lower level” [22]. Once a budget exists, no inner layer is allowed to opt back into waiting forever, which is precisely the loophole that makes ad-hoc timeout stacks leak.

Decision: may an edge proxy honour a client's requested timeout?

Chosen (Envoy at the edge)
  • No, not implicitly. The proxy's own timers win unless an operator opts in via config
  • An untrusted client must not be able to set its own budget on your infrastructure
Rejected (PR #5294)
  • Auto-disable the stream idle timeout whenever a grpc-timeout header is present
  • mattklein123: “allows a potentially untrusted request to override a timeout, which at the edge is not safe” [14]
Flips when
  • The hop is internal and the client is trusted. Between your own services, honouring the propagated deadline is the whole point; at the untrusted edge it is a denial-of-service vector, so the same behaviour is right inside and wrong outside

PR #5294 is the rejected decision worth studying, because it shows the trust boundary changing the answer to an otherwise identical question. The author proposed that Envoy disable its stream idle timeout for gRPC requests, reasoning that such requests are already bounded by their grpc-timeout header, so the proxy's timer is redundant and harmful. Maintainer mattklein123 closed it: at the edge, letting the request set the timeout “is not safe,” and any such behaviour must be “opt-in based on config” [14]. The reasoning is sound and the consequence is a footgun: because Envoy will not trust the header, its 15-second route default silently truncates long streaming RPCs, which is exactly the bug users kept filing (see failures, below). Both the rejection and the resulting pain are correct at once, which is what makes it a real trade-off rather than a mistake.

DecisionChosenRejectedBecauseFlips whenEvidence
Default valueFinite (5–60s)Infinite / unsetA hang is worse than a spurious timeoutYou ship a general library, not an apphttpx / Go #24138
Chain governancePropagated deadlinePer-call timeoutLeaf must not outlive root; bounds retries tooCall depth is 1gRPC / MongoDB CSOT
Edge trustProxy timers winHonour client headerUntrusted client can't set your budgetHop is internal, client trustedEnvoy #5294
Enforce a documented timeout that never workedEnforce it, gate + warnKeep the bug on by default“kubelet should do what is specified in the API”Never; correctness wins, opt-out providedk8s #97057
Out of budget, response missingRetry within remaining deadlineHedge a parallel copyHedging multiplies load; only safe if idempotentRead-only op and tail latency dominatesgRFC A6

Figure 3 · Decision tree: which timeout does this call actually need?

yes, depth ≥ 2

no, single leaf

yes

no, untrusted edge

yes

no

Does this call fan out
to other services?

Is the next hop
trusted / internal?

Set one finite
per-operation timeout

Propagate a deadline;
each hop decrements it

Proxy sets its own timer;
ignore client's header

Is the response
a stream?

Bound with idle +
max-duration, NOT overall

Bound with overall
deadline; cancel on expiry

yes, depth ≥ 2

no, single leaf

yes

no, untrusted edge

yes

no

Does this call fan out
to other services?

Is the next hop
trusted / internal?

Set one finite
per-operation timeout

Propagate a deadline;
each hop decrements it

Proxy sets its own timer;
ignore client's header

Is the response
a stream?

Bound with idle +
max-duration, NOT overall

Bound with overall
deadline; cancel on expiry

The terminal nodes are actions, not “it depends.” The left branch is the one home-grown stacks skip. Derived from the decision table above and MongoDB's CSOT rationale.
Diagram source
The key idea

A timeout is a local resource-protection decision; a deadline is a distributed correctness decision. Systems that only have timeouts protect each node while letting the call tree do orphaned work. The move from “every service has a timeout” to “the request carries a budget” is the single highest-leverage change in this whole area, and it is the one home-grown stacks almost always skip.

04

What broke in production

Three failure classes account for nearly every timeout incident in the public record: the timeout that fired but was the symptom not the cause; the default nobody chose that cut off healthy work; and the timeout that was documented but never enforced. GitLab's tracker and the Kubernetes and Envoy issue threads are unusually candid on all three.

Figure 4 · Failure path: the proxy default that outranks your deadline

"Streaming server""Envoy (default 15s)""gRPC client""Streaming server""Envoy (default 15s)""gRPC client"15s route timeout elapsesmid-streamdeadline had 9m45s left,the proxy default wonLotsOfReplies, deadline 10 minforward,x-envoy-expected-rq-timeout-ms15000stream chunk 1stream chunk 2 ...reset upstream connection"UNAVAILABLE: upstreamrequest timeout"
"Streaming server""Envoy (default 15s)""gRPC client""Streaming server""Envoy (default 15s)""gRPC client"15s route timeout elapsesmid-streamdeadline had 9m45s left,the proxy default wonLotsOfReplies, deadline 10 minforward,x-envoy-expected-rq-timeout-ms15000stream chunk 1stream chunk 2 ...reset upstream connection"UNAVAILABLE: upstreamrequest timeout"
The client set a generous deadline; the intermediary's unconfigured 15-second route timeout fired first and returned an error the client never asked for. Reconstructed from envoyproxy/envoy #17697 and the Envoy timeout FAQ.
Diagram source
Postmortem

The statement timeout that was the symptom, not the cause

AssumptionA per-statement timeout protects the database, so if it fires the timeout did its job.
What happenedA PostgreSQL 11 optimizer bug made queries with an IN (..) returning >200 rows slow from milliseconds to over two minutes; those queries exhausted the connection pool and the whole site went dark. The statement timeout fired everywhere, which looked like the problem and was actually the alarm.
Blast radiusFull GitLab.com outage, 05:19–07:53 UTC, ~2.5 hours (154 minutes); traffic fell 65%. Fixed in the moment by running ANALYZE namespaces;.
FixSplit complex queries, replace the pathological IN (..) pattern, and add alerting on abnormally long query execution, not just on the timeout firing.
Design ruleA timeout is a detector, not a cure. If your timeout is firing in bulk, the number to change is upstream of it; treating “raise the timeout” as the fix hides the regression that is actually killing you.
Postmortem

One slow transaction, one shared table, thirty minutes of blast radius

AssumptionA slow operation on one table hurts only that table's users.
What happenedA long-running transaction on the webhooks table produced database timeouts; because “so many transactions involve webhooks this problem had a large blast radius” and degraded the whole front end.
Blast radiusSite-wide slow responses and job-processing delays, 09:48–10:16 UTC (30 minutes), with knock-on delays to 11:25 UTC.
FixChase down the lock/contention on the hot table; the timeout was again the messenger, firing on innocent transactions that merely touched the same rows.
Design ruleA timeout's blast radius is the shared resource behind it, not the slow caller. Size and place timeouts by asking “what else waits on this lock, pool or table,” because that is who your timeout will actually page.
Postmortem

The 15-second default nobody chose cutting off healthy streams

AssumptionIf I set my gRPC client's deadline generously, my streaming RPC will run as long as it needs.
What happenedAn Envoy proxy in the path applied its default 15-second route timeout, and the server-streaming RPC failed with UNAVAILABLE: upstream request timeout. The request carried x-envoy-expected-rq-timeout-ms: 15000, a value the developer never set. Envoy will not trust the client's grpc-timeout at the edge (see PR #5294), so its own default won.
Blast radiusEvery long-lived streaming call through the proxy, cut at 15 seconds, three years after the FAQ documented the trap.
FixExplicitly set the route timeout to 0s for streaming routes, or bound them with max_stream_duration or an idle timeout instead of the overall one.
Design ruleEvery layer in the path has its own default, and the tightest one wins regardless of what you configured at the edges. Enumerate the proxies and meshes between client and server before trusting any timeout you set at one end.
Field report

The probe timeout that was in the API and never ran

AssumptionKubernetes enforces the timeoutSeconds I set on an exec probe.
What happenedFor years the kubelet silently ignored the timeout on exec probes: a probe command that ran long was still treated as healthy, so a hung container stayed “ready.” Users reproduced it across 1.15, 1.16, 1.17 and 1.18, noting Docker's own healthcheck honoured the same limit on the same image.
Blast radiusAny workload relying on exec-probe timeouts to detect a wedged process, on every cluster, until Kubernetes 1.20.
FixKEP-1972 made the kubelet respect the timeout, shipped GA-on-by-default behind an ExecProbeTimeout gate with warning events so operators could find who had been relying on the bug.
Design ruleA timeout you configured is a hypothesis until you have watched it fire. Test the expiry path explicitly; a setting the platform accepts but never enforces is worse than no setting, because it buys false confidence.

The fourth incident opens onto a decision worth its own paragraph, because it shows how dangerous fixing a timeout bug can be. When KEP-1972 finally made the kubelet enforce the documented 1-second default, a large population of clusters had unknowingly depended on the bug, running probes that took longer. Microsoft's AKS team proposed PR #97057 to ship the fix disabled by default, arguing that enforcing it suddenly “puts those previously operational Kubernetes environments at risk” and “will be breaking a very large number of folks” [20]. SIG Node refused: andrewsykim answered that “the kubelet should really do what is specified in the API” and derekwaynecarr chose to “leave the feature gate on and enable opt out” [20]. The compromise, correctness by default with an escape hatch and loud warning events, is the template for changing any long-wrong timeout: enforce the right behaviour, but give operators a season and a signal to discover their dependence on the old one.

The pattern under all four

A timeout is only ever a proxy for a resource you are protecting: a connection, a thread, a lock, a pod's liveness. Three of these incidents fired the timeout correctly and still hurt, because the operators read the timeout as the problem rather than as an alarm about the resource behind it. The fourth hurt because the timeout was documented but never wired to anything. Debug a timeout by naming the resource it guards, not by editing the number.

05

Numbers you can plan against

The defaults shipped by the frameworks an architect actually uses, and the incident figures that show what those defaults cost when left unexamined. All are read directly from source, docs or incident reviews, dated to the point of check.

Setting / metricValueSystemContextAs ofSource
Default request timeoutnonerequests (Python)Waits forever unless caller sets one; unresolved since 20162026-09#3070
Default request timeout5.0 shttpx (Python)Shipped in code, the answer requests declined2026-09source
Default connect/read timeout60 sbotocore (AWS SDK)Applied when caller passes none2026-09source
http.DefaultClient timeoutnoneGo net/http&Client{}; zero means infinity2026-09#22982
Route (overall) timeout15 sEnvoyBites streaming responses that never end2026-09FAQ
Stream idle timeout5 minEnvoyFires when no bytes move on the stream2026-09FAQ
Connection idle timeout1 hEnvoyUpstream connection with no active streams2026-09FAQ
gRPC deadline (unset)infinitegRPC“wait effectively forever” if omitted2026-09guide
Server default request timeoutnone→300 sNode.js httpRemoved in v13, reinstated at 5 min in v18 for DoS safety2026-09http.md
Server headers timeoutmin(reqTimeout, 60 s)Node.js httpSlowloris protection when no reverse proxy fronts it2026-09http.md
statement_timeout0 (off)PostgreSQLGlobal setting explicitly discouraged2026-09config.sgml
Hedge maxAttempts ceiling5gRPC (gRFC A6)Values >5 clamped to 5 without error2026-09A6
Outage from ms→min query slowdown154 minGitLab.comPool exhaustion; traffic −65%2021-03incident
Degradation from one hot-table txn30 minGitLab.comWebhooks table, wide blast radius2021-09incident
Read these carefully

Every figure here is measured from a primary artefact: a constant in source, a value in official docs, or a number in an incident review. None are vendor performance claims. Two caveats. First, defaults move: Node's server request timeout went from none to 300s between v13 and v18, so pin the version before trusting a number. Second, GitLab's statement_timeout was set to 10s in a 2016 decision record [4]; the later incidents are not evidence that timeout was wrong, but that a timeout cannot compensate for a query-planner regression or a hot-table lock.

06

The evidence wall

Every source behind this page, graded. The mix is deliberately weighted to the repository record, code, issues, pull requests, specs and incident trackers, because the research network reached those and not the engineering-blog layer. Filter by kind.

PostmortemGitLab2021-03

High number of database statement timeouts

A PostgreSQL 11 optimizer bug slowed IN (..) queries from ms to >2 min, exhausting the pool and taking the whole site down for 154 minutes.

Carry forwardA timeout firing in bulk is an alarm about an upstream regression, not the thing to raise.
gitlab.com/…/production/-/issues/3875
PostmortemGitLab2021-09

Postgres statement timeouts from one long transaction

A long transaction on the webhooks table caused database timeouts with a “large blast radius” because so many transactions touch webhooks; 30 minutes of site-wide degradation.

Carry forwardA timeout's real blast radius is the shared resource behind it, not the slow caller.
gitlab.com/…/production/-/issues/5493
PostmortemGitLab2022-03

Post-deploy migration failing with statement timeout

The request-path statement timeout also fires on maintenance: a schema migration hit it and needed a purpose-built index to complete.

Carry forwardThe timeout that protects online traffic can block your migrations; batch and maintenance paths need their own budget.
gitlab.com/…/production/-/issues/6737
SourceEnvoy2021-08

gRPC server streaming: UNAVAILABLE, upstream request timeout

A streaming RPC through Envoy died at 15s carrying x-envoy-expected-rq-timeout-ms: 15000, a value the developer never set.

Carry forwardEnumerate every proxy in the path; the tightest default wins over what you set at the ends.
github.com/envoyproxy/envoy/issues/17697
SourceEnvoy2018-12

PR #5294: disable stream idle timeout for gRPC (closed unmerged)

Rejected because honouring a client's grpc-timeout at the edge “is not safe”; must be opt-in per config.

Carry forwardDeadline propagation is right between trusted services and a DoS vector at the untrusted edge.
github.com/envoyproxy/envoy/pull/5294
SourceKubernetes2020-08

#94080: exec probes ignore timeout

Reproduced across four minor versions; a documented timeoutSeconds the kubelet silently never enforced.

Carry forwardA configured timeout is a hypothesis until you have watched it fire.
github.com/kubernetes/kubernetes/issues/94080
SourceKubernetes / AKS2020-12

PR #97057: ship ExecProbeTimeout as false (closed unmerged)

A vendor asked to keep the bug on by default to avoid mass breakage; SIG Node chose correctness with an opt-out and warning events.

Carry forwardTo fix a long-wrong timeout: enforce it by default, but give a season and a loud signal to find who relied on the old behaviour.
github.com/kubernetes/kubernetes/pull/97057
SourcePython / requests2016–24

#3070 & PR #6709: the default-timeout debate

Issue closed to the “Bankruptcy” milestone in 2016; a 2024 PR to add 10s/30s defaults still open, milestone slipped then dropped.

Carry forwardYou cannot inherit a safe default from a library whose authors cannot agree on one; the decision is yours.
github.com/psf/requests/pull/6709
SourceGo2018–19

#24138 / #22982 / #31657: net/http timeout defaults

Zero-means-infinity called a bad default by rsc in 2018 (still open); DefaultClient has none; and a set timeout that wasn't visible downstream let goroutines pile up.

Carry forwardA timeout the deadline doesn't reach still leaks work; make the budget visible to every layer, not just the caller.
github.com/golang/go/issues/24138
SourceNode.js2019-05

PR #27558: remove default server timeout

Deleted the 2-minute default as semver-major because it was “surprising and problematic” and “specific to Node.js.” Shipped v13.

Carry forwardAn idle-style timer on a healthy long connection is a bug source; separate “no progress” from “too long overall.”
github.com/nodejs/node/pull/27558
Sourcehttpx / botocore2026-09

Shipped finite defaults

DEFAULT_TIMEOUT_CONFIG = Timeout(timeout=5.0) in httpx; DEFAULT_TIMEOUT = 60 in botocore. The answer the older cores wouldn't commit to.

Carry forwardA finite default is a legitimate, shippable choice; “no default” is a decision too, and usually the worse one.
github.com/encode/httpx/…/_config.py
SourceTwitter / Finagle2026-09

Deadline as a broadcast Context

“A broadcast Context that represents when the request should be completed by,” propagated automatically across service boundaries.

Carry forwardMake the deadline ambient, not a parameter to remember; that is the difference between a mechanism that works and one that works when people remember it.
github.com/twitter/finagle/…/Contexts.rst
Decision recordMongoDBAccepted

Client Side Operations Timeout

Collapses five-plus additive timeout knobs into one whole-operation budget, and forbids un-setting it once specified at any level.

Carry forwardOne budget beats many knobs, and no inner layer may opt back into waiting forever.
github.com/mongodb/specifications/…
Decision recordgRPC2024-08

gRFC A6: retry and hedging design

Hedging sends the same RPC again after hedgingDelay without waiting for failure, bounded by the one call deadline and a failure-ratio throttle; maxAttempts capped at 5.

Carry forwardHedging trades load for tail latency and is only safe on idempotent calls; the deadline still bounds the whole fan-out.
github.com/grpc/proposal/…/A6-client-retries.md
Decision recordKubernetes2020-09

KEP-1972: kubelet exec probe timeouts

Makes the kubelet honour the documented probe timeout, GA-on-by-default with a gate and warning events for those who relied on the bug.

Carry forwardWarning events before enforcement let operators discover a hidden dependence on old behaviour without an outage.
github.com/kubernetes/enhancements/…/1972
Decision recordGitLab2016-06

Set statement_timeout to a non-infinite time

GitLab.com ran with statement_timeout = 0 (infinite) until this record set it to 10s, with backup tasks overriding back to 0.

Carry forwardEven large operators start at “infinite” by default; and one global number needs per-workload carve-outs for batch and backup.
gitlab.com/…/production-engineering/-/work_items/40
Decision recordgRPC2026-09

PROTOCOL-HTTP2: the grpc-timeout header

Defines the deadline on the wire, and that an omitted timeout means the server should assume infinite.

Carry forwardThe budget is a first-class wire concept, not a client convention; absence is defined as “forever.”
github.com/grpc/grpc/…/PROTOCOL-HTTP2.md
DocsEnvoy2026-09

How do I configure timeouts?

The canonical catalogue: route 15s, stream idle 5min, connection idle 1h, request headers disabled by default; explicit warnings about streaming.

Carry forwardThere is no “the timeout”; there is a stack, and the unset ones keep vendor defaults you didn't choose.
github.com/envoyproxy/envoy/…/timeouts.rst
DocsgRPC2026-09

Deadlines guide

No default deadline; propagation opt-in in C++, default in Java/Go; deadline shipped as remaining-time to survive clock skew.

Carry forwardSet a realistic deadline validated by load testing; never rely on the framework default, which is “forever.”
github.com/grpc/grpc.io/…/deadlines.md
DocsNode.js2026-09

http.md: requestTimeout & headersTimeout

Server request timeout none→300s in v18; headers timeout defaults to min(reqTimeout, 60s); must be non-zero for Slowloris protection without a reverse proxy.

Carry forwardRemoving a bad default and re-adding a considered one at a different layer is a legitimate arc; pin the version.
github.com/nodejs/node/…/http.md
DocsPostgreSQL2026-09

config.sgml: statement_timeout

Defaults to 0 (disabled) and warns against a global setting because “it would affect all sessions.”

Carry forwardThe database defers the decision to you on purpose; bound queries at the connection or role, not the cluster.
github.com/postgres/postgres/…/config.sgml
SourceAWS / botocore2026-09

httpsession.py: DEFAULT_TIMEOUT = 60

The AWS SDK for Python applies a 60-second connect and read default when the caller passes none.

Carry forwardEven a “safe” 60s default is often too loose for an interactive path; inherit nothing without checking it against your latency budget.
github.com/boto/botocore/…/httpsession.py
07

Build a miniature, then productionise it

Six rungs from a hang you can watch to a deadline you can prove. The line from toy to real is at rung 3, where the timeout stops being local and starts being a budget the whole chain shares.

Reproduce the hang

Write a client that calls a server which sleeps forever, using your language's default HTTP or RPC client with no timeout set. Watch the caller block.

Done when: the client is stuck and you can point to the exact line that owns the frozen thread.  Teaches: “no timeout” is a decision, and it is the default in more places than you expect.

Separate idle from overall

Add a read/idle timeout and, separately, an overall timeout. Make the server dribble one byte every few seconds so the idle timer never fires but the overall one does. Then make it go fully silent so only the idle timer fires.

Done when: you can trigger each timeout independently and name which is which.  Teaches: why Node removed then re-added its server timer, and why Envoy needs both a stream idle and a route timeout.

Carry a deadline across two hops

Insert a middle service: client → A → B. Pass a deadline from the client, have A subtract the time it already spent before calling B, and have B refuse work if the remaining budget is negative.

Done when: a slow A leaves B with less budget, and a client-side deadline of zero means B is never called.  Teaches: the deadline, not the timeout, is the unit that composes; this is the rung that crosses from toy to real.

Cancel the orphaned work

When the deadline expires at the client, propagate cancellation so B actually stops. Instrument B to log whether it kept running after the client gave up.

Done when: B's log shows it stopped, not that it finished work nobody read.  Teaches: the gRPC server “automatically cancelling a call” is the difference between a deadline and a lie; an expired deadline with no cancellation still burns the resource.

Retry inside the budget, and prove it can't storm

Add retries with jitter, but bound the total by the remaining deadline rather than a fixed attempt count. Then hedge a second read-only request after a delay and cancel the loser.

Done when: a saturated dependency does not receive more load under retry than under no-retry, and hedging only fires on the slow tail.  Teaches: gRFC A6's rule that the call deadline bounds the whole retry/hedge fan-out; hedging trades load for tail latency and only on idempotent calls.

Test the expiry path in CI

Add a test that asserts the timeout actually fires: inject a slow dependency and assert the call fails within the budget, not that it eventually returns. Assert cancellation reached the leaf.

Done when: the test fails if someone removes the timeout or breaks propagation.  Teaches: the Kubernetes exec-probe lesson, a timeout the platform accepts but never enforces is worse than none, so the enforcement itself is what you test.

08

Keep hunting

The queries that surfaced this material. The repository-record ones are the highest yield here, because the network reached code hosts when it could not reach blogs; adapt them to your own stack's frameworks.

Defaults and the arguments about them

  • <library> default timeout issue "no default" OR "zero means"
  • repo:<org>/<repo> is:pr is:closed is:unmerged timeout default
  • DEFAULT_TIMEOUT path:**/*.py OR path:**/*.go
  • "surprising and problematic" OR "not common in other" timeout

Deadlines and propagation

  • grpc-timeout OR "deadline propagation" path:doc
  • "broadcast context" OR "request deadline" <framework>
  • "client side operations timeout" OR "operation timeout" spec

Incidents where a timeout was the symptom

  • gl-infra/production statement_timeout OR "database timeouts" incident
  • "upstream request timeout" OR "expected-rq-timeout" issue
  • <proxy> 15s OR "route timeout" streaming broke

Timeouts that were documented but never ran

  • <platform> timeout "not respected" OR "ignored" issue
  • feature gate timeout "on by default" OR "opt out" KEP OR RFC
  • repo:<org>/<repo> is:issue timeout "does not" OR "silently"
09

References

  1. GitLab, 2021-03-09 High number of database statement timeouts (incident review)GitLab Infrastructure. Checked 2026-09-22.
  2. GitLab, 2021-09-07 Postgres transactions showing high rate of statement timeoutsGitLab Infrastructure. Checked 2026-09-22.
  3. GitLab, 2022-03-31 Post-deploy migration failing with statement timeoutGitLab Infrastructure. Checked 2026-09-22.
  4. GitLab, Set PostgreSQL statement_timeout to a non infinite timeGitLab Production Engineering, 2016-06. Checked 2026-09-22.
  5. requests, Consider making Timeout option required or have a default (#3070)Python Software Foundation, 2016. Checked 2026-09-22.
  6. requests, Add default timeout (PR #6709)Python Software Foundation, 2024. Checked 2026-09-22.
  7. httpx, _config.py (DEFAULT_TIMEOUT_CONFIG)Encode. Checked 2026-09-22.
  8. Go, net/http: make default configs have better timeouts (#24138)The Go Authors, 2018. Checked 2026-09-22.
  9. Go, net/http: add InactivityTimeout to http.DefaultClient (#22982)The Go Authors, 2017. Checked 2026-09-22.
  10. Go, net/http: Client.Timeout is not propagated to Request's Context Deadline (#31657)The Go Authors, 2019. Checked 2026-09-22.
  11. Node.js, http, http2: remove default server timeout (PR #27558)Node.js, 2019. Checked 2026-09-22.
  12. Node.js, HTTP API documentation (requestTimeout, headersTimeout)Node.js. Checked 2026-09-22.
  13. Envoy, FAQ: How do I configure timeouts?Envoy project. Checked 2026-09-22.
  14. Envoy, conn_manager: Disable stream idle timeout for gRPC requests (PR #5294, closed unmerged)Envoy project, 2018. Checked 2026-09-22.
  15. Envoy, gRPC server streaming getting UNAVAILABLE: upstream request timeout (#17697)Envoy project, 2021. Checked 2026-09-22.
  16. gRPC, PROTOCOL-HTTP2 (grpc-timeout wire format)gRPC Authors. Checked 2026-09-22.
  17. gRPC, Deadlines guide (source)gRPC Authors. Checked 2026-09-22.
  18. gRPC, gRFC A6: gRPC Retry DesignNoah Eisen & Eric Gribkoff, last updated 2024-08. Checked 2026-09-22.
  19. Kubernetes, KEP-1972: Kubelet Exec Probe TimeoutsKubernetes SIG Node, 2020. Checked 2026-09-22.
  20. Kubernetes, kubelet: ship new ExecProbeTimeout featuregate as false (PR #97057, closed unmerged)Kubernetes, 2020. Checked 2026-09-22.
  21. Kubernetes, exec-type liveness or readiness probes ignore timeout (#94080)Kubernetes, 2020. Checked 2026-09-22.
  22. MongoDB, Client Side Operations Timeout specificationMongoDB. Checked 2026-09-22.
  23. Finagle, Contexts (Deadline broadcast context)Twitter. Checked 2026-09-22.
  24. botocore, httpsession.py (DEFAULT_TIMEOUT)Amazon Web Services. Checked 2026-09-22.
  25. PostgreSQL, config.sgml (statement_timeout, transaction_timeout)PostgreSQL Global Development Group. Checked 2026-09-22.