# Request Header Or Cookie Too Large: Fix 400 and 431

> Fix 400 Request Header Or Cookie Too Large and 431 errors: find the oversized cookie or token, then check nginx, Apache, Node, ALB and Cloudflare limits.

Source: https://howhttpworks.com/debug/request-header-too-large
Last reviewed: 2026-10-04

Error messages this page covers:
- `400 Bad Request: Request Header Or Cookie Too Large`
- `431 Request Header Fields Too Large`
- `HTTP ERROR 431`
- `Your browser sent a request that this server could not understand. Size of a request header field exceeds server limit.`
- `client sent too long header line`

> **TL;DR:** The request's headers, almost always the `Cookie` line or an `Authorization` token, exceeded a server limit. nginx answers `400 Request Header Or Cookie Too Large` when one header line exceeds an 8 KB buffer (`large_client_header_buffers 4 8k`), Apache answers 400 above 8,190 bytes per field, and Node answers `431` above 16 KiB in total. Clearing the site's cookies fixes it for one user; for everyone, find the cookie that grew, stop it growing, and only then raise limits.

## What it means

The client sent more header bytes than some server in the path will buffer. RFC 6585 defines `431 Request Header Fields Too Large` for this, and allows it both when the total is too large and when a single field is. In practice most servers still answer `400`, so the status code alone does not tell you much. The body and the limit do:

| Layer | Limit (default) | Status and what the client sees |
| --- | --- | --- |
| nginx | One header line must fit in one 8 KB buffer; the whole header in 4 of them (`large_client_header_buffers 4 8k`) | `400`, page title `400 Request Header Or Cookie Too Large` |
| Apache httpd | 8,190 bytes per header field (`LimitRequestFieldSize`), 100 fields (`LimitRequestFields`) | `400`, "Size of a request header field exceeds server limit." |
| Node.js (`http`, Express, Next.js, Vite dev servers) | 16 KiB for all headers together (`--max-http-header-size`) | `431` with an empty body; Chrome shows `HTTP ERROR 431` |
| AWS Application Load Balancer | 16 KB request line, 16 KB per header, 64 KB in total; not adjustable | `400` |
| Cloudflare | 128 KB of request headers in total, 16 KB URL | An error page served by Cloudflare itself (check for a Cloudflare error page, not your origin's) |

The nginx response, which reaches the browser even when Cloudflare or another CDN sits in front:

```http
HTTP/1.1 400 Bad Request
Server: nginx
Content-Type: text/html
Connection: close

<html>
<head><title>400 Request Header Or Cookie Too Large</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<center>Request Header Or Cookie Too Large</center>
```

The Node response is just two lines, which is why the browser shows its own error page instead of a server page:

```http
HTTP/1.1 431 Request Header Fields Too Large
Connection: close
```

## Who sent it?

- Page title `400 Request Header Or Cookie Too Large` and `Server: nginx`: nginx, either your own or an ingress controller. A Cloudflare `CF-Ray` header on the same response only means Cloudflare relayed it; with a 128 KB allowance, Cloudflare is rarely the layer that refused.
- "Size of a request header field exceeds server limit." in an Apache-styled page: Apache httpd.
- Empty body, `431`, "HTTP ERROR 431" in Chrome: a Node.js server, very often a local dev server.
- `Server: awselb/2.0` with a 400: the ALB rejected it before your targets saw anything. Its limits cannot be raised, so the only fix is smaller headers.

The logs are quiet by default. nginx records `client sent too long header line: "Cookie: ..."` at the `info` level, and Apache records `AH00561: Request header exceeds LimitRequestFieldSize: Cookie` at `info` too. With the usual `error_log ... error;` and `LogLevel warn`, nothing appears. Lower the level temporarily to see which header is at fault:

```nginx
error_log /var/log/nginx/error.log info;
```

## Why headers get that big

- **Cookies on a parent domain.** A cookie set with `Domain=example.com` is sent to `www.example.com`, `app.example.com`, `api.example.com` and every other subdomain. Each team's analytics, A/B testing, consent and feature-flag cookies add up on every host, and nobody owns the total. Scope cookies to the host that needs them by leaving out [`Domain`](https://howhttpworks.com/cookies/domain).
- **State stored in cookies.** A JWT or serialized session with user roles, group memberships or a cart grows with the user. A cookie can legally hold about 4 KB of name and value, and RFC 6265bis asks browsers to keep at least 50 cookies per domain, so the browser will happily store far more than an 8 KB nginx buffer can take.
- **Large `Authorization` headers.** Bearer tokens that embed group claims grow with the user's group count. Apache's own documentation notes that SPNEGO (Kerberos) headers can reach 12,392 bytes, already above its 8,190-byte default.
- **Long URLs and `Referer`.** A long query string makes the request line and the `Referer` of every subresource request long. Same-origin requests send the full URL as `Referer` under the default `strict-origin-when-cross-origin` policy. In nginx an over-long request line returns [414 URI Too Long](https://howhttpworks.com/status-codes/414), not 400.
- **localhost.** Cookies do not provide isolation by port (RFC 6265 Section 8.5), so every project you have run on `localhost` shares one cookie jar.

## Measure it

In the DevTools console on the affected site:

```javascript
// Total bytes of cookies visible to JavaScript on this page
new Blob([document.cookie]).size

// The ten largest cookies visible to JavaScript
document.cookie.split('; ')
  .map((c) => [c.split('=')[0], c.length])
  .sort((a, b) => b[1] - a[1])
  .slice(0, 10)
```

`document.cookie` does not include `HttpOnly` cookies, which are usually the session and auth cookies, so treat the number as a minimum. For the full list open DevTools, Application, Cookies: the Size column shows each cookie's size in bytes, including HttpOnly ones. The Network panel shows the exact `Cookie` line the browser sent with the failing request.

From the command line, `curl -v` prints the request headers with `>`. Paste the browser's Cookie value into a file and count what a request actually carries:

```bash
curl -sv -o /dev/null -H "Cookie: $(cat cookies.txt)" https://example.com/ 2>&1 \
  | grep '^> ' | wc -c
```

To reproduce the error without a browser, send a single 9,000-byte header:

```bash
curl -s -o /dev/null -w '%{http_code}\n' \
  -H "Cookie: big=$(head -c 9000 /dev/zero | tr '\0' a)" https://example.com/
```

nginx with default settings returns `400`. Splitting the value into several cookies does not help: over HTTP/1.1 the browser sends all cookies on one `Cookie` line, so their combined size has to fit in a single 8 KB buffer, not in the 32 KB that four buffers add up to.

## Fix it, in order of likelihood

1. **Unblock the user**: delete the site's cookies, including those on the parent domain, and reload. Support staff can usually give this instruction faster than you can deploy anything.
2. **Find the offender** with the DevTools Size column or the `info`-level log line, which names the header.
3. **Stop the growth**: remove cookies nobody reads, set short `Max-Age` on marketing and experiment cookies, scope cookies to a single host, and replace a cookie-borne JWT or session blob with an opaque session ID (see below).
4. **Raise limits only where needed and in matching order**, so the first layer does not accept something the next one rejects.

### nginx

`large_client_header_buffers` is valid only in `http` and `server` blocks, not in `location`. When it is set in a `server` block, nginx may use the value from the default server for that address and port, because the virtual host is picked from the `Host` header after the header has been read. Setting it in `http` avoids that trap.

```nginx
http {
    # Default: 4 8k. One header line must fit in one buffer.
    large_client_header_buffers 4 16k;
}
```

Reload with `nginx -t && nginx -s reload`. The same limits apply to HTTP/2 and HTTP/3, where a buffer limits each header field after HPACK or QPACK compression. Check `nginx -T | grep large_client_header_buffers` to confirm which value is loaded.

If nginx proxies to Node, raising nginx to `16k` per line does not help a request whose total exceeds Node's 16 KiB; Node then answers 431 behind nginx.

### Kubernetes ingress-nginx

The controller ConfigMap key is `large-client-header-buffers`, default `"4 8k"`:

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  large-client-header-buffers: "4 16k"
```

### Apache httpd

```apache
# Defaults: 8190 bytes per field, 100 fields. Server config or virtual host only.
LimitRequestFieldSize 16380
LimitRequestLine      16380
```

With name-based virtual hosts the value from the first-listed virtual host for the address and port is used, so set it there or globally.

### Node.js, Express and dev servers

```bash
# CLI flag
node --max-http-header-size=32768 server.js

# Works for tools that start Node for you (next dev, vite, nodemon)
NODE_OPTIONS=--max-http-header-size=32768 npm run dev
```

```javascript
import http from 'node:http'

// Per-server override of the 16 KiB default
const server = http.createServer({ maxHeaderSize: 32768 }, app)
server.listen(3000)
```

For the localhost case, clearing cookies for `localhost` is the real fix.

### Response headers: the other direction

A large `Set-Cookie` coming back from your app can break nginx too, with a different symptom: `upstream sent too big header while reading response header from upstream` in the error log and a [502 Bad Gateway](https://howhttpworks.com/debug/nginx-502-bad-gateway). That is controlled by `proxy_buffer_size` (one memory page, 4k or 8k by default), not by `large_client_header_buffers`.

## Why raising limits is a stopgap

Raising a limit hides the growth rather than stopping it. Every byte in a parent-domain cookie is sent with every request to every subdomain, including requests for images, scripts and API calls. A user who adds groups or keeps a long-lived session keeps adding bytes until they hit the next limit, and some limits cannot be raised at all: the ALB's 16 KB per header is fixed. Every proxy, WAF and API gateway on the path also has its own ceiling, and you may not control all of them.

The durable fix is to keep state on the server:

```http
HTTP/1.1 200 OK
Set-Cookie: __Host-sid=8f3a1c9e2b7d4f60; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=86400
```

The cookie holds a random ID, and the session data (roles, cart, preferences) lives in Redis or the database, keyed by that ID. The `__Host-` prefix forbids a `Domain` attribute, so the cookie cannot spread to sibling subdomains. For APIs that need a self-contained token, keep the claims minimal and look permissions up server-side instead of embedding every group in the token.

To clear bloated cookies for users who are already locked out, serve a response with [`Clear-Site-Data: "cookies"`](https://howhttpworks.com/headers/clear-site-data) from a host that can still accept their requests. MDN describes its scope as the registered domain, including subdomains.

## Reproduce and verify

```bash
# Before the fix: 400 from nginx, 431 from Node
curl -s -o /dev/null -w '%{http_code}\n' \
  -H "Cookie: big=$(head -c 9000 /dev/zero | tr '\0' a)" https://example.com/

# Straight to the origin, skipping the CDN or load balancer
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: example.com' \
  -H "Cookie: big=$(head -c 9000 /dev/zero | tr '\0' a)" http://ORIGIN_IP/
```

If the origin accepts the request and the public URL rejects it, the limit is in front of the origin. After shrinking the cookies, the DevTools Size column total for the site should sit comfortably below the smallest limit in your path.

## Related

- [431 Request Header Fields Too Large](https://howhttpworks.com/status-codes/431) covers the status code itself.
- [Cookie](https://howhttpworks.com/headers/cookie) and [Set-Cookie](https://howhttpworks.com/headers/set-cookie) explain how the header is built and which attributes decide where cookies are sent.
- [Cookie prefixes](https://howhttpworks.com/cookies/cookie-prefixes) (`__Host-` keeps a cookie on one host) and [Max-Age](https://howhttpworks.com/cookies/max-age) (stale cookies expire) limit where cookies spread and how long they pile up.
- [Sessions and state](https://howhttpworks.com/guides/sessions-and-state) compares server-side sessions with tokens stored in cookies.
- [413 Content Too Large](https://howhttpworks.com/debug/nginx-413-request-entity-too-large) is the body-size counterpart.
