skip to content

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