skip to content

Keep-Alive and Connection Caps

Keep-alive is on by default, SetKeepAlivesEnabled turns it off while draining, and nothing in http.Server bounds how many connections you accept. Interviewers ask where you would cap concurrency.

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

questions

5

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

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

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

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