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
- 400 Bad Request
- 401 Unauthorized
- 402 Payment Required
- 403 Forbidden
- 404 Not Found
- 405 Method Not Allowed
- 406 Not Acceptable
- 407 Proxy Authentication Required
- 408 Request Timeout
- 409 Conflict
- 410 Gone
- 411 Length Required
- 412 Precondition Failed
- 413 Content Too Large
- 414 URI Too Long
- 415 Unsupported Media Type
- 416 Range Not Satisfiable
- 417 Expectation Failed
- 418 (Unused)
- 419 Page Expired
- 421 Misdirected Request
- 422 Unprocessable Content
- 423 Locked
- 424 Failed Dependency
- 425 Too Early
- 426 Upgrade Required
- 428 Precondition Required
- 429 Too Many Requests
- 431 Request Header Fields Too Large
- 444 No Response
- 451 Unavailable For Legal Reasons
- 495 SSL Certificate Error
- 496 SSL Certificate Required
- 497 HTTP Request Sent to HTTPS Port
- 499 Client Closed Request