skip to content

When http.Server.ReadHeaderTimeout and IdleTimeout are left zero but ReadTimeout is set, what bounds those phases?

level: middleimportance: should knowfreq 44%

answer

  1. two of the four fields inherit
  2. zero does not always mean unbounded
  3. the value they inherit is ReadTimeout
  4. zero ReadTimeout breaks the inheritance chain
  5. negative is how you opt out on purpose

basics

~20 s

Both fall back to ReadTimeout. A zero ReadHeaderTimeout uses ReadTimeout for the header read, and a zero IdleTimeout uses it for the keep-alive wait, so setting ReadTimeout alone silently bounds three phases with one number.

solid answer

~50 s

The two fields are documented as falling back: if `ReadHeaderTimeout` is zero the value of `ReadTimeout` is used, and if both are zero there is no header timeout; if `IdleTimeout` is zero the value of `ReadTimeout` is used, while a negative `IdleTimeout` — or zero when `ReadTimeout` is also zero — means no idle timeout. So `ReadTimeout: 30 * time.Second` and nothing else gives you a 30 second header deadline, a 30 second whole-request deadline and a 30 second idle deadline at once. The trap runs the other way. A popular configuration is a short `ReadHeaderTimeout` with `ReadTimeout` left at zero, so handlers can police their own body reads — and that configuration leaves `IdleTimeout` unbounded too, because the fallback it reaches for is the zero `ReadTimeout`. Whenever `ReadTimeout` is zero, `IdleTimeout` has to be set explicitly.

go deeper

for a junior

Recall that ReadHeaderTimeout and IdleTimeout borrow ReadTimeout's value when left at zero, so a single ReadTimeout is doing more work than its name suggests.

for a middle

State both fallback rules precisely, including that the chain breaks when ReadTimeout is itself zero, and that negative IdleTimeout means deliberately unbounded.

for a senior

Recognise the configuration that looks hardened and is not: short header timeout, zero ReadTimeout for long uploads, and an idle phase left unbounded by the broken fallback.

for a principal

Decide how a service's idle window relates to the proxy in front of it, and make the shared server constructor set IdleTimeout explicitly so no team inherits a zero by accident.

## The two fallbacks, stated exactly Two of `http.Server`'s four timeout fields do not simply mean *unbounded* when zero. They defer to `ReadTimeout`: - **`ReadHeaderTimeout`** — the time allowed to read request headers. If it is zero, the value of `ReadTimeout` is used. If both are zero, there is no timeout. After the headers are read the connection's read deadline is reset, so the handler can decide what counts as too slow for the body. - **`IdleTimeout`** — the maximum time to wait for the next request when keep-alives are enabled. If it is zero, the value of `ReadTimeout` is used. If it is negative, or zero while `ReadTimeout` is also zero, there is no timeout. `ReadTimeout` and `WriteTimeout` have no such fallback: zero or negative means no timeout, full stop. ## One number, three phases The practical consequence of the first rule is reassuring. A service that sets only `ReadTimeout` is not as exposed as it looks, because the same number is quietly bounding the header read and the idle wait too. Someone who has set `ReadTimeout` on a public endpoint has, without knowing it, bounded a client that connects and never finishes its request line. The practical consequence of the second rule is the trap, and it is the reason this question gets asked. The recommended shape for services that accept large or streaming request bodies is: - a short `ReadHeaderTimeout`, because headers should always arrive promptly, and - `ReadTimeout` left at zero, so a long upload is not killed by a whole-request deadline and the handler polices the body itself. That configuration is right about the read side and silently wrong about the idle side. `IdleTimeout` looks for `ReadTimeout`, finds zero, and ends up unbounded — so every kept-alive connection that finishes a request and then goes quiet stays open forever, holding its goroutine and buffers. A service can be perfectly hardened against slow headers and still accumulate idle connections without limit. The rule to carry away: **the moment you set `ReadTimeout` to zero, `IdleTimeout` becomes mandatory.** ## Negative versus zero Only `IdleTimeout` distinguishes the two usefully. Zero means *inherit*; negative means *no timeout, and I mean it*. That distinction exists so you can express `ReadTimeout: 30 * time.Second, IdleTimeout: -1` — bound each request tightly, keep connections alive indefinitely between them — which the zero value cannot express, since zero would give you 30 seconds. It is a small piece of API design worth recognising: a field with a fallback needs a third state to opt out of the fallback, and Go spends the negative range on it. ## Picking the numbers Sensible reasoning, rather than copied constants: - **Header phase**: measured in seconds and independent of what the endpoint does. Nothing legitimate takes long to send headers. This is the field that protects a connection before any handler runs, so it should be short even on endpoints where every other bound is relaxed. - **Whole request**: sized to the largest legitimate body at the slowest legitimate upload rate, or zero with the handler taking responsibility instead. - **Idle**: sized in relation to whatever sits in front of the service. If a proxy or load balancer keeps connections warm, the server closing first produces a race where the proxy dispatches a request onto a connection the server is retiring; giving the server the longer idle window makes the proxy the one that retires connections, which is the side that can retry cleanly. ## How to check what you actually configured The fallbacks are invisible in the struct literal, which is why they bite. The reliable check is behavioural rather than by reading: open a connection, send nothing, and see whether it is closed; complete one request, then go quiet, and see whether it is closed. Two probes settle what four fields and two fallback rules actually add up to on this server.

  • A server sets ReadHeaderTimeout to 5 seconds and leaves ReadTimeout and IdleTimeout at zero. What is unbounded?
    Two things. The request body has no whole-request deadline, which is often deliberate, and the idle keep-alive wait is unbounded as well, which usually is not: `IdleTimeout` falls back to `ReadTimeout`, and that is zero here. Set `IdleTimeout` explicitly in this configuration.
  • Why does IdleTimeout distinguish negative from zero when ReadTimeout does not?
    Because `IdleTimeout` has a fallback and `ReadTimeout` does not. A field that inherits when zero needs some other value to mean deliberately unbounded, so the negative range is spent on it: `IdleTimeout: -1` keeps connections alive between requests even when `ReadTimeout` is set.
  • Does setting ReadTimeout alone protect a connection that never finishes its request line?
    Yes. `ReadHeaderTimeout` falls back to `ReadTimeout`, so the header read inherits that deadline and the connection is closed when it expires. A dedicated, shorter `ReadHeaderTimeout` is still better, because headers deserve a much tighter budget than a whole request body.

saying these in an interview costs you the question

  • Says every zero timeout field means unbounded
  • Sets a short ReadHeaderTimeout and forgets IdleTimeout entirely
  • Thinks ReadTimeout falls back to ReadHeaderTimeout rather than the reverse
  • Treats negative IdleTimeout as invalid or as an instant close
  • Assumes WriteTimeout inherits from ReadTimeout too