skip to content

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

level: middleimportance: should knowfreq 40%

answer

  1. not the body
  2. the request line counts too
  3. zero is not unlimited
  4. the handler never sees the request
  5. a 4xx about header fields

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.

solid answer

~50 s

`Server.MaxHeaderBytes` is the ceiling on how many bytes `net/http` will read while parsing the request line plus all header keys and values of one request. It explicitly does not bound the request body — that needs a separate mechanism. Zero means the package default, `http.DefaultMaxHeaderBytes`, which is 1 MB. When a client blows past it the server does not invoke your handler at all: it writes `431 Request Header Fields Too Large` and closes the connection. The reason to lower it is that the ceiling is per connection and applies before any handler exists, so on a server holding thousands of connections it is the worst case an unauthenticated caller can force you to buffer. Most APIs need a few kilobytes; the risk in lowering it is legitimate clients with fat cookies, long bearer tokens or many proxy-added forwarding headers.

code

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

// Request line + header keys and values, per request. Zero would mean
// http.DefaultMaxHeaderBytes, which is 1 << 20 (1 MB).
srv.MaxHeaderBytes = 16 << 10
log.Fatal(srv.ListenAndServe())

go deeper

for a junior

Know that the field exists on http.Server, that it covers the request line and headers rather than the body, and that leaving it zero gives you a one-megabyte default rather than no limit.

for a middle

Explain the enforcement point: parsing happens before the handler, so an oversized request produces a bare 431 and a closed connection with nothing in your handler logs.

for a senior

Justify a number. Tie the ceiling to the number of connections you accept to get a worst-case header budget, and name the client-side realities — cookies, tokens, proxy-added headers — that decide how low you can safely go.

for a principal

Own the compatibility side of the trade: lowering the ceiling changes what callers you can serve, so the decision needs measurement of real traffic, a rollout that can be reverted, and someone accountable when a partner's requests start failing.

## What the field actually counts `http.Server.MaxHeaderBytes` is an `int` field: ```go srv := &http.Server{Addr: ":8080", Handler: mux} srv.MaxHeaderBytes = 16 << 10 ``` It controls the maximum number of bytes the server will read while parsing **the request line and the header keys and values** of a single request. `GET /v1/events?tenant=42 HTTP/1.1` counts. `Authorization: Bearer …` counts, key and value. The request body does **not** count, and neither does the body of any later request on the same connection — the budget is per request. ## The default Leave the field at its zero value and the server uses `http.DefaultMaxHeaderBytes`, which is `1 << 20` — one megabyte. That is generous. Almost no API needs a megabyte of headers; the default exists so that nothing legitimate breaks out of the box. ## What happens on overflow The limit is enforced while the server is still reading the request, before a `*http.Request` is handed to anything. If the headers do not fit, the server writes a bare `431 Request Header Fields Too Large` response and closes the connection. Three consequences worth stating out loud: 1. **Your handler never runs.** No middleware, no logging you wired into the handler chain, no metrics recorded there. If you want visibility into 431s you need it somewhere other than the handler. 2. **The connection is not reused.** A client that keeps sending oversized headers keeps paying for new connections. 3. **It is not a body limit.** A client can still stream a huge body at you; bounding that is a separate decision with a separate mechanism. ## Why this field belongs to a conversation about connection capacity On a server that holds many simultaneous connections, `MaxHeaderBytes` is the per-connection worst case that an *unauthenticated* peer can force. It is not what every connection costs — the server only grows its parsing buffer as far as the headers it actually receives, so ordinary clients sending 1 KB of headers cost 1 KB. It is the ceiling on what a hostile or broken client can make one connection hold before the server gives up. Multiply the ceiling by the number of connections you are willing to accept and you have the pessimistic header budget for the instance. Left at 1 MB with thousands of connections allowed, that number is embarrassing; set to 16 KB it is a rounding error. That is the real argument for lowering it: not that it fixes a leak, but that it turns an unbounded-looking exposure into an arithmetic you can defend. ## Choosing a number Measure before you choose. Real header sizes are driven by things you may not control: * session cookies, especially several of them set by different subsystems; * bearer tokens — a signed token with a lot of claims is not small; * forwarding headers added by every proxy in front of you, which accumulate; * content negotiation and tracing headers. A public API behind two proxies with cookie-based auth can legitimately send several kilobytes. 8–16 KB is a common landing spot; 1 KB will break somebody. And the failure mode of setting it too low is nasty to debug from the client side: a 431 with no body, no handler log line, and a closed connection. ## Related knobs Recent Go added `Server.MaxHeaderValueCount` alongside it, a separate cap so that a request cannot present an unreasonable *number* of header values even while each one is small — byte size and value count are different attack shapes. Note also that `MaxHeaderBytes` is a server field: it says nothing about responses your service receives when it acts as a client. ## Interview summary "Request line plus header keys and values, per request, body excluded. Zero means `DefaultMaxHeaderBytes`, 1 MB. Over the limit the server writes 431 and closes without calling the handler. I lower it to what the API actually needs, because it is the per-connection worst case an anonymous client can force, and I check real header sizes first so I do not break clients with big cookies or tokens."

  • Why can a 431 from a Go server be hard to diagnose from your own logs?
    Because the limit is enforced during request parsing, before any handler or middleware runs. Nothing in your handler chain sees the request, so handler-level logging and metrics record nothing. You need logging outside the handler — or a report from the client — to notice it at all.
  • Does MaxHeaderBytes protect you from a client streaming a huge request body?
    No. The field is explicit that it bounds header parsing only. A body limit is a separate decision, applied around the body reader, and a service that lowered MaxHeaderBytes but left bodies unbounded has fixed the smaller of the two exposures.
  • What real clients break when you set the limit to one or two kilobytes?
    Clients carrying several session cookies, long signed bearer tokens with many claims, or requests that have passed through proxies that each append forwarding and tracing headers. Measure the real distribution of header sizes before picking a number.

saying these in an interview costs you the question

  • Thinks MaxHeaderBytes also limits the request body
  • Says zero means no limit rather than the 1 MB default
  • Expects the handler to run and inspect the oversized request
  • Answers 413 Payload Too Large instead of 431
  • Sets it to a kilobyte without checking real header sizes