What does an http.Server bound when ReadTimeout, WriteTimeout and IdleTimeout are all left at zero?
answer
- the zero value is not a default
- net/http adds no hidden number
- one goroutine parked per open connection
- the one-line helper builds a bare Server
- no deadline set means no deadline enforced
basics
~10 sNothing. Zero means no timeout on every http.Server timeout field, so a connection may spend forever sending a request, receiving a response, or sitting idle. The convenience helper http.ListenAndServe builds exactly such a server.
solid answer
~40 sZero is not a sensible default, it is the absence of a deadline. `http.Server` has four timeout fields: `ReadHeaderTimeout` bounds reading the request headers, `ReadTimeout` bounds reading the whole request including the body, `WriteTimeout` bounds the write side of the response, and `IdleTimeout` bounds how long a kept-alive connection may wait for the next request. A zero value on any of them means no timeout is applied, and net/http adds no hidden default of its own. That matters because the server runs one goroutine per accepted connection, so a client that connects and then stalls holds a goroutine and its buffers indefinitely. The fix is to construct the `http.Server` yourself with explicit values rather than calling `http.ListenAndServe`, which internally builds `&http.Server{Addr: addr, Handler: handler}` and leaves every timeout at zero.
code
go · 13 lines// Unbounded: this builds &http.Server{Addr: ":8080", Handler: mux} internally.
log.Fatal(http.ListenAndServe(":8080", mux))
// Bounded: every phase gets an explicit deadline.
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 90 * time.Second,
}
log.Fatal(srv.ListenAndServe())go deeper
Be ready to name the four http.Server timeout fields and say what each phase covers, and to state plainly that zero means no timeout rather than a built-in default.
Explain the mechanism: these are deadlines on the connection, one goroutine serves each connection, and an unbounded phase parks that goroutine for as long as the client likes.
Show that you construct the server explicitly in every service you own, and that you can say which field you would tighten first on an endpoint exposed to untrusted clients.
Own the argument that the safe default belongs in a shared constructor rather than in a wiki page, so no team ships an unbounded listener by writing the shortest line that compiles.
## The four fields `http.Server` exposes four `time.Duration` fields that bound different phases of a connection's life: - **`ReadHeaderTimeout`** — how long the server will spend reading the request line and headers of a request. - **`ReadTimeout`** — how long it will spend reading the *entire* request, headers plus body. - **`WriteTimeout`** — the deadline for writing the response; its clock starts once that request's headers have been read. - **`IdleTimeout`** — how long a keep-alive connection may sit between requests before the server closes it. Each one is applied by setting a read or write deadline on the underlying network connection. When a deadline expires, the read or write fails and the server closes the connection. ## What zero means For `ReadTimeout` and `WriteTimeout` the documented meaning of a zero or negative value is plain: **there is no timeout**. For the other two the zero value falls back rather than disabling: 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, and if `IdleTimeout` is negative — or zero while `ReadTimeout` is also zero — there is no idle timeout. So a server literal with nothing but `Addr` and `Handler` set bounds **no phase at all**. A client can open a socket, send half a request line, and stay there until the process restarts or the operating system tears the connection down. The Go standard library deliberately ships no default here, because a sensible number depends entirely on what the service does. ## Why the absence of a bound costs something net/http serves each accepted connection on its own goroutine. That goroutine is parked in a blocking read while it waits for the request. It costs a small stack plus the buffered reader and writer attached to the connection, and, more importantly, it is never reclaimed while the connection stays open, because there is nothing to reclaim it: the goroutine is not leaking through a bug, it is doing exactly what it was told, waiting forever for bytes that never come. On a public endpoint that is a cheap thing for an untrusted client to abuse. A few thousand connections that each send one byte of header per minute cost the attacker almost nothing and cost the server a goroutine, two buffers and a file descriptor apiece. Request throughput can stay perfectly flat while the resident goroutine count climbs in a straight line. ## What to set instead Construct the server explicitly: - `ReadHeaderTimeout` short — a few seconds. No legitimate client needs longer to send its headers, and this is the single field that bounds a connection before any handler runs. - `ReadTimeout` sized to the largest legitimate request body, or left at zero deliberately when the handler itself polices the body. - `WriteTimeout` sized to the slowest legitimate response, remembering that it also covers the client's download, not just the server's work. - `IdleTimeout` set explicitly whenever `ReadTimeout` is zero, because otherwise the fallback gives you nothing. A useful mental model: these are **connection** deadlines, not handler deadlines. They are enforced by the network layer beneath your handler, they do not stop a running handler, and when one fires the client sees a closed or truncated connection rather than an HTTP status code. Bounding how long a *handler* may run is a separate mechanism. ## The helper functions `http.ListenAndServe(addr, handler)` and `http.ListenAndServeTLS` are documented convenience wrappers, and they are convenient precisely because they construct the server for you — with every timeout field at its zero value. They are fine for a local experiment or an internal tool behind something else. For a process that will accept connections from clients you do not control, build the `http.Server` value yourself; it is four extra lines and it is the difference between a bounded and an unbounded process.
- Can you set these fields when you call http.ListenAndServe?No. That helper takes only an address and a handler and constructs `&http.Server{Addr: addr, Handler: handler}` internally, so every timeout field stays at zero. To set them you build the `http.Server` value yourself and call its own `ListenAndServe` method.
- What does a negative IdleTimeout mean, as opposed to zero?Negative means no idle timeout at all, stated explicitly. Zero means fall back to `ReadTimeout`, and only if `ReadTimeout` is also zero does it end up unbounded. So negative is how you opt out of the fallback deliberately rather than by accident.
- If a deadline expires, what does the client receive?Not an HTTP status. These are deadlines on the underlying connection, so the read or write fails and the server closes the socket. The client sees a reset or truncated response. Anything that needs a real status code has to be done above the connection layer.
saying these in an interview costs you the question
- Assumes net/http applies a sensible default timeout
- Says the request context will cancel a stalled connection
- Thinks a proxy in front removes the need for server timeouts
- Treats zero and negative as meaning the same thing
- Believes an expired deadline returns a 5xx status to the client