Skip to content
Control Plane Labs

494 Request Header Too Large

Client error response defined by nginx.org.

Last updated July 27, 2026

Common causes at a glance

  • A session or auth cookie that has grown past 8 KB
  • A JWT or SAML assertion passed in a header rather than a body
  • Kerberos or NTLM negotiate tokens for a user in many AD groups
  • Accumulated tracking cookies on a shared parent domain

Reproduce it

curl -s -w '%{http_code}\n' -H "X-Junk: $(printf 'a%.0s' {1..9000})" https://www.example.com/

What 494 Request Header Too Large means

nginx reads request headers into fixed buffers allocated up front. A small one sized by client_header_buffer_size handles the common case; anything larger spills into the pool defined by large_client_header_buffers, four buffers of 8k by default. Two limits follow: no single header line may exceed one buffer, and the request line plus all headers must fit in the pool.

Exceeding either aborts the request. The client normally receives 400 Bad Request, because 494 is an internal marker. It becomes visible with an error_page 494 rule, and appears in the access log either way.

Where it comes from

494 has no RFC and no IANA registration. It exists in the nginx source as an internal code so header overflow can be told apart from other 400s, and the limits that trigger it are documented under large_client_header_buffers and client_header_buffer_size. The standardised code for this condition is 431, which nginx does not send. RFC 9110 §15 tells a client meeting an unrecognised 4xx to treat it as 400, so exposing 494 gains nothing at the protocol level; its value is diagnostic, separating header bloat from malformed requests in your logs.

What actually causes it

Cookies, nearly always. One application rarely sends 8 KB of headers, but a domain shared by several teams accumulates them: analytics, feature flags, an oversized session blob, a legacy cookie nobody will delete. The browser sends all of them to every host under the parent domain, so the endpoint that breaks first is often not the one that set the offending cookie. The other reliable source is credentials in headers — an Authorization: Bearer holding a JWT stuffed with claims, or a SPNEGO token for a user in many groups. Both grow silently and then break only a subset of users, which is what makes it awkward to triage.

How to debug it

Reproduce with a header of known size, then bisect down until it passes to find the real ceiling:

curl -s -o /dev/null -w '%{http_code}\n' \
-H "X-Junk: $(printf 'a%.0s' {1..9000})" https://www.example.com/

A 400 there with a clean 200 for a smaller header confirms buffer exhaustion. On the server, error.log at info level records “client sent too long header line”, naming the header that broke. Measure a real client before raising limits — the header inspector shows what a browser actually sends. Bumping to large_client_header_buffers 4 16k; is a two-minute fix, but if a cookie is already at 8 KB, the durable fix is moving that state server-side.

Working examples

curl

# Force the condition with a single oversized header.
curl -s -o /dev/null -w '%{http_code}\n' \
  -H "X-Junk: $(printf 'a%.0s' {1..9000})" https://www.example.com/

# Compare against a header that fits comfortably.
curl -s -o /dev/null -w '%{http_code}\n' \
  -H "X-Junk: $(printf 'a%.0s' {1..2000})" https://www.example.com/

# nginx side:
#   client_header_buffer_size 1k;
#   large_client_header_buffers 4 8k;

Python (requests)

import requests

url = "https://www.example.com/"

# Bisect to find the actual ceiling rather than guessing at the config.
for size in (1000, 4000, 8000, 12000, 16000, 32000):
    try:
        code = requests.get(url, headers={"X-Junk": "a" * size}, timeout=10).status_code
    except requests.exceptions.RequestException as exc:
        code = type(exc).__name__
    print(size, "bytes ->", code)

# 400 from nginx here is 494 internally; check access.log for the real code.

Node (fetch)

// Total header size matters, not just the largest single line.
const url = "https://www.example.com/";

for (const size of [1000, 4000, 8000, 12000, 16000]) {
  try {
    const res = await fetch(url, { headers: { "X-Junk": "a".repeat(size) } });
    console.log(size, "->", res.status);
  } catch (err) {
    console.log(size, "->", err.cause?.code ?? err.message);
  }
}

// Cookies count toward the same budget:
const cookies = "a=1; ".repeat(2000);
console.log((await fetch(url, { headers: { Cookie: cookies } })).status);

Reference and tooling

The authoritative list of assigned status codes is theIANA HTTP Status Code Registry. For a searchable copy with the semantics summarised, use theHTTP status code reference on this site. To see exactly what a server is sending, theheader inspector shows the raw status line and response headers, and thecurl builder assembles the request flags for you.

Other 4xx codes