skip to content

Server Lifecycle and Limits

A zero-value http.Server has no read, write or idle timeout and will hold a connection forever. Shutdown, keep-alive and connection caps decide how a service behaves under load and during a deploy.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

explore

questions

20

Does Go's http.Server reuse a client connection for later requests by default, and how do you stop it?

level: juniorimportance: must knowfreq 45%

answer

  1. the default is already on
  2. you can only switch it off
  3. one server-wide switch, one response header
  4. a method on Server, not a field
  5. SetKeepAlivesEnabled versus a per-response header

basics

~20 s

Go's http.Server enables HTTP keep-alives by default, so one accepted TCP connection serves many requests. Server.SetKeepAlivesEnabled(false) turns them off for the whole server; setting a Connection: close response header in a handler closes only that one connection.

solid answer

~40 s

Yes — `net/http` turns keep-alives on by default, so an HTTP/1.1 client that has been served once can send its next request on the same TCP connection, and the server holds that connection open waiting for it. There are two levers. `srv.SetKeepAlivesEnabled(false)` is the server-wide switch: from then on the server answers with a `Connection: close` token and closes the socket after each response. Inside a single handler, `w.Header().Set("Connection", "close")` closes just that connection after that response, leaving every other client's connection pooled. The stdlib docs are blunt that the global switch is for very resource-constrained servers only, because without reuse every request pays a fresh TCP and TLS handshake, a new accept and a new serving goroutine.

code

go · 5 lines
go
srv := &http.Server{Addr: ":8080", Handler: mux}

// Only for very resource-constrained servers: reuse is otherwise on by default.
srv.SetKeepAlivesEnabled(false)
log.Fatal(srv.ListenAndServe())

go deeper

for a junior

Recall the default (on) and both switches by name: Server.SetKeepAlivesEnabled for the whole server, a Connection: close response header for one connection. Say which one you would reach for and why.

for a middle

Explain what the server does differently in each mode: the token it writes, when the socket closes, and what a repeat client then pays in handshakes, accepts and goroutines.

for a senior

Show that you know reuse is why connection count and request rate are separate numbers, and treat disabling keep-alives as a last-resort memory-for-CPU trade rather than a routine hardening step.

for a principal

Be ready to argue when a fleet should give up reuse at all, given that the alternative is more instances or a cap elsewhere, and to say who bears the latency cost of that decision.

## What "keep-alive" means on the server side When Go's `net/http` server accepts a TCP connection it starts one goroutine to serve it. With HTTP/1.1 that goroutine does not stop after one request/response pair: it loops, reading another request off the same socket. That reuse is what "keep-alive" (persistent connections) means from the server's point of view, and it is the reason a Go server's *connection count* is a different number from its *request rate*. ## The default Keep-alives are **on by default** for any `http.Server` you construct, and for `http.ListenAndServe`. You do not enable them; you can only disable them. HTTP/1.1 clients get reuse automatically. An HTTP/1.0 client only gets it if it explicitly asks with a `Connection: keep-alive` request header, and the server answers in kind. ## Lever one: the server-wide switch ```go srv := &http.Server{Addr: ":8080", Handler: mux} srv.SetKeepAlivesEnabled(false) ``` `Server.SetKeepAlivesEnabled(bool)` is a method, not a struct field, and it is safe to call while the server is running. With it set to false the server stops reusing connections: each response carries a `Connection: close` token and the socket is closed once the response is written. The `net/http` documentation says in as many words that only very resource-constrained environments, or servers that are shutting down, should do this. ## Lever two: one response at a time A handler can close just its own connection: ```go w.Header().Set("Connection", "close") ``` The server notices the token while writing the response header, emits `Connection: close` on the wire and closes that connection afterwards. Every other connection the server holds is unaffected. This is the surgical version — useful for evicting one abusive or wedged client, or for a request you know left the connection in a state you do not want to reuse. ## What it costs to turn reuse off Every request then pays: * a TCP three-way handshake, and a full TLS handshake if the listener is TLS; * an `accept` on the listener and a fresh serving goroutine with its own stack; * a new set of per-connection read and write buffers. On a chatty client that is a large latency and CPU tax, and it pushes socket churn onto the client too. So disabling keep-alives is a memory-for-CPU trade, not a free safety measure. ## Why the default matters for capacity Because reuse is on, the number of connections a server holds is decided by whoever is talking to it, not by its own request rate. Put an ingest endpoint behind a load balancer that maintains a pool of upstream connections and the server can sit on thousands of mostly idle connections while serving a modest number of requests per second. Each of those connections is real memory: a serving goroutine and its stack, plus roughly a few kilobytes of read and write buffering, plus TLS state if the connection is encrypted. That is the thing to internalise early: `http.Server` has **no field that caps how many connections it will hold**. Keep-alives being on by default is exactly why the count can climb far above anything the request rate suggests, and why a Go HTTP server's memory can be dominated by connections rather than by handler work. ## Common confusions * Disabling keep-alives does not stop the server from serving concurrent requests — concurrency comes from one goroutine per connection either way. * It does not change how bodies are framed or streamed; responses still stream. * Setting the header in one handler does not disable reuse globally, and calling `SetKeepAlivesEnabled(false)` does not tear down connections already in flight — it changes how subsequent responses are answered. ## What to say in an interview "On by default; `SetKeepAlivesEnabled(false)` is the global off switch and the docs reserve it for very constrained servers; `w.Header().Set(\"Connection\", \"close\")` closes one connection. And because reuse is the default, connection count is a capacity dimension of its own."

  • What does the server actually put on the wire once keep-alives are disabled?
    Each response carries a `Connection: close` token and the server closes the socket after writing it. An HTTP/1.1 client that wants to send another request has to open a new connection, so it pays a handshake per request.
  • Why do the net/http docs warn against disabling keep-alives?
    Because reuse is what makes repeated requests cheap. Without it every request costs a TCP handshake, a TLS handshake on a secure listener, an accept, a new serving goroutine and fresh per-connection buffers. The docs reserve it for very resource-constrained environments.
  • How would you close only the connections belonging to one misbehaving client?
    Set `Connection: close` in the response header for the requests you can identify as theirs. The server closes that connection after the response; every other client keeps its pooled connection, so you have not paid a fleet-wide handshake tax to evict one caller.

saying these in an interview costs you the question

  • Says keep-alives must be explicitly enabled on http.Server
  • Thinks a handler setting Connection: close disables reuse server-wide
  • Claims disabling keep-alives is free because handshakes are cheap
  • Answers about the outbound HTTP client instead of http.Server
  • Believes disabling keep-alives makes the server single-threaded
open as a page

What does a dependency-free liveness http.HandlerFunc in a Go service do, and what must it not touch?

level: juniorimportance: must knowfreq 58%

basics

~20 s

A liveness handler reports only that the process is alive and still serving HTTP. In Go it is a tiny http.HandlerFunc that writes 200 OK and touches nothing else: no locks, no database, no outbound calls.

open as a page

In Go's net/http, how does Server.Shutdown differ from Server.Close?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Server.Shutdown stops accepting new connections, closes idle ones, and waits for in-flight requests to finish; it takes a context that bounds the wait. Server.Close closes every connection immediately, cutting requests off mid-response.

open as a page

What does an http.Server bound when ReadTimeout, WriteTimeout and IdleTimeout are all left at zero?

level: juniorimportance: must knowfreq 72%

basics

~10 s

Nothing. 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.

open as a page

Why does ListenAndServe return http.ErrServerClosed as soon as Server.Shutdown begins?

level: middleimportance: must knowfreq 58%

basics

~20 s

The first thing Server.Shutdown does is close the listeners, and that is what makes ListenAndServe return. It reports that accepting has stopped, not that the drain is done — the drain ends later, when Shutdown itself returns.

open as a page

What span does http.Server.WriteTimeout cover, and why is it not a deadline on handler execution?

level: middleimportance: must knowfreq 58%

basics

~20 s

http.Server.WriteTimeout starts when that request's headers have been read, so it covers the handler reading the body and the whole response reaching the client. It is a deadline on the connection: writes fail when it expires, but the handler goroutine keeps running.

open as a page

What does http.Server.MaxHeaderBytes bound in Go, and what does the server answer when a request exceeds it?

level: middleimportance: should knowfreq 40%

basics

~20 s

MaxHeaderBytes caps the bytes an http.Server reads parsing a request's line and header keys and values, never the body. Zero means http.DefaultMaxHeaderBytes, one megabyte. Exceeding it gets a 431 and a closed connection, with the handler never called.

open as a page

How do you gate a Go readiness handler on an atomic.Bool so it answers 503 once the process starts draining?

level: middleimportance: should knowfreq 47%

basics

~20 s

Keep one package-level atomic.Bool for readiness. Store true when warm-up finishes and false as soon as the process begins draining; the handler calls Load and writes 200 when true, or Retry-After plus 503 Service Unavailable when false.

open as a page

What does http.Server.Shutdown actually wait for, and what happens when its context expires?

level: middleimportance: should knowfreq 52%

basics

~10 s

Shutdown waits on connections, not goroutines: it closes listeners and idle connections, then waits for active ones to finish. If the context expires it returns that error and stops waiting, leaving those connections open.

open as a page

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

level: middleimportance: should knowfreq 44%

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.

open as a page

Go's http.Server has no max-connection setting: how do you cap an ingest tier that OOMs holding thousands of keep-alive connections?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Prove with a live-heap profile that the memory is per-connection buffering, not a handler leak, then cap connections at the listener, because http.Server has no such field. Also lower MaxHeaderBytes and count idle connections with the ConnState hook.

open as a page

Why do requests still reach a Go instance after its readiness handler starts returning 503 during a rollout?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Because the routing layer learns about the 503 only after its next probe fails and that decision propagates to every proxy. The instance must keep serving normally for longer than that lag before it stops accepting, or it drops in-flight traffic.

open as a page

Your Go report service's Server.Shutdown never returns and deploys hang. How do you diagnose it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Shutdown returns only when every tracked connection has gone idle, so an unbounded Shutdown blocks forever on one handler that never returns. Capture the goroutine profile with full stacks while it hangs, then bound the context and escalate to Close.

open as a page

Your Go http.Server's goroutine count climbs linearly under a slow-header probe while RPS stays flat — what is happening and which field fixes it?

level: seniorimportance: should knowfreq 50%

basics

~10 s

Each connection the probe opens parks a per-connection goroutine blocked reading request headers that never complete, so nothing is dispatched and RPS stays flat while goroutines accumulate. Set http.Server.ReadHeaderTimeout, and IdleTimeout alongside it.

open as a page

Who owns the connection cap on a memory-bound Go http.Server fleet, and how do you set the number?

level: principalimportance: should knowfreq 30%

basics

~20 s

Derive the cap from measured bytes per open connection against the instance's memory budget, decide whether over-capacity queues silently or is refused visibly, and name one authoritative place it lives, with an owner and an alarm before it binds.

open as a page

How long should Server.Shutdown's drain window be for a Go service deployed many times a day?

level: principalimportance: should knowfreq 28%

basics

~20 s

Derive it from the request-duration distribution, cap it below whatever kill deadline the deploy tooling enforces, and always escalate to Server.Close when it expires. The number is a negotiated cost split between slow deploys and cut requests, not a technical constant.

open as a page

What does an http.Server's ConnState hook report in Go, and what are its connection states?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

Server.ConnState is a callback net/http invokes on every connection state change, receiving the net.Conn and an http.ConnState. The states are StateNew, StateActive, StateIdle, StateHijacked and StateClosed. Use it to count held connections and the idle share.

open as a page

What does http.Server.RegisterOnShutdown do, and why must its function not block?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

RegisterOnShutdown records a callback that Server.Shutdown fires, each in its own goroutine, right after it closes the listeners. Shutdown never waits for those callbacks and ignores what they do, so they are a notification hook, not part of the drain.

open as a page

How do you decide whether a Go service's liveness handler may check a shared dependency like its database?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Decide by what a restart can fix. A dependency check makes every replica fail liveness at the same moment during one shared outage, so the default is no dependency I/O in liveness; put shared state in readiness or in alerts instead.

open as a page

For http.Server's four timeout fields, how do you set a fleet-wide default and decide which endpoints get an exemption?

level: principalimportance: nice to knowfreq 33%

basics

~20 s

Ship the default as a shared server constructor rather than a written guideline, keep the header deadline short and non-negotiable, and grant exemptions per response or per listener instead of loosening the fleet number for everyone.

open as a page