Development

Reading a 431 or 414 in Logs: When a Cursor Busts a Limit

Antonio Evans11 min read

A split scene rendered as a clean technical diagram.
Key points

Key points

  1. 1A 431 means the request header block (usually cookies) exceeded the server limit, while a 414 means the request target (path plus query string) was too long.
  2. 2nginx defaults both limits to 8 KB and reports a large header as a 400, so logs need to be read at the message level, not the status code alone.
  3. 3The fastest triage field is the raw request line or the cookie header size, and the fastest fix is usually trimming what the client sends, not raising the buffer.
  4. 4Alert on the rate of 431 and 414 per endpoint, page the service owner when a short-window rate crosses a baseline-derived threshold, and route bot-shaped traffic to the security queue.

First, a note on pagination

The title is a pun worth disarming early, because two different failures get called "a cursor busts a limit." One is pagination: a client walks a deep result set and the query slows to a crawl. The other is transport: the client sends a request the server refuses to parse because a header or URL is too long. This article is about the second kind. If your actual problem is that page 5,000 of a listing times out, that is offset pagination scanning and discarding rows. PostgreSQL reads every row up to the offset before returning the page, and a benchmark on a 10 million row table puts page 10,000 at roughly 8.2 seconds against 8 milliseconds for page 1 per this pagination guide. Keyset pagination seeks to a known position instead of counting, which is why teams move to it as datasets grow as Contentful describes. And if your cursor is a long opaque string that you place in a URL, the two problems meet: the cursor itself can push a request line past the server limit, which is exactly the 414 case below.

What each status code actually says

The status codes are precise, even if the prose around them is not. A 431 Request Header Fields Too Large means the server is unwilling to process the request because its header fields are too large, and it applies both when the combined set is oversized and when a single field is at fault per MDN. It was defined in RFC 6585 alongside 428, 429 and 511, and the spec asks servers to say which field was too large, though many do not as this 431 explainer notes. MDN lists the two most common triggers as an overlong Referer URL and too many cookies again from MDN.

A 414 means the request target was too long: the path plus query string, not the headers and not the body. The HTTP specification does not mandate a hard URI limit, so the ceiling is whatever the server or the edge sets per this 414 guide. The causes it names are a POST that got converted to a GET, redirect loops that keep appending parameters, and deliberate abuse.

A 413 is a third code people confuse with both: it means the request body was too large, typically an upload, and it has nothing to do with headers or URLs per this comparison. If a client sends a huge cookie and gets a 413 in response, the server is answering the wrong question. The correct response for an oversized header is 431, not 414 (the URI was not the problem) and not 413 (a GET has no body) as this Stack Overflow answer sets out.

CodeWhat exceeded the limitTypical triggerWho fixes it
431Combined or single request headersCookie accumulation, long Referer, SSO claims in headersSite owner trims cookies; visitor clears them
414Request target: path plus query stringGET carrying form data, redirect chains, long cursor in URLWhoever builds the URL, by moving data to a body or shortening the cursor
413Request bodyFile upload over the body limitUploader sends less; owner raises the body limit
400Malformed request, or nginx's generic reply to oversized headersOversized header on nginxSame as 431

Where the limits come from

HTTP itself defines no maximum header size; servers impose their own. Apache's default is 8 KB and IIS allows 16 KB per this long-running Stack Overflow answer. nginx caps each request line at 8 KB through its large_client_header_buffers directive, which defaults to four buffers of 8 KB, and logs client sent too long URI while reading client request line when the request line exceeds it per this 414 guide. Apache's equivalent is LimitRequestLine, defaulting to 8190 bytes from the same guide.

Node.js is worth flagging because it changed its default: a security fix lowered the maximum header size to 8 KB, which broke users passing large data in headers per the Node.js issue. The same thread notes nginx's 8 KB request line cap. Enterprise products make the tradeoff explicit: the Digital.ai documentation says 8 KB makes it hard to pass authentication tokens and scopes, and documents server.max-http-request-header-size as the knob, recommending teams start at the default and raise it in increments of 16 KB or 32 KB after watching logs per that documentation.

The single most useful production fact is that nginx reports an oversized header as 400 Bad Request: Request Header Or Cookie Too Large rather than a 431, so the same condition appears under two status codes depending on which server answered per this 431 explainer. A dashboard that filters on status = 431 will miss every nginx-fronted instance of the problem, which is the most common way teams discover the error weeks late.

Reading the failure in logs and dev tools

Work outward from the edge. At the proxy, capture the status, the request line, the cookie header, the Referer and the total request length. In nginx, $request_length covers the request line plus headers plus body, so it tells you a request was big but not which part. Pair it with $request (the request line) and $http_cookie (the cookie header) to split the diagnosis. In Apache, the error log entry for an oversized header plus the %r format string gives you the request line. At the application layer, log the header byte count and the URL length separately on any request you reject, because that is the one place you control the shape of the data.

In browser dev tools, check Network, select the failing request, and read the request headers pane. A request whose Cookie header runs to several kilobytes is a 431 candidate. One whose query string runs to thousands of characters is a 414 candidate. If the browser sends nothing and you still see an error, the failure is client-side or at a local proxy and the server logs will be empty, which is itself diagnostic.

The reproduction steps differ by code:

  • For a 431, build a request with an oversized Cookie header of 9 KB and send it to nginx. Expect a 400 with the header-too-large message, not a 431. Repeat against the application directly, bypassing nginx, and you will likely see a 431 from the framework.
  • For a 414, append a 10 KB query string to any endpoint and send it. Expect client sent too long URI while reading client request line in the error log.
  • For both, compare $request_length against the byte size your alert rule assumes, so the threshold is grounded in real traffic rather than a guess.

One more log field earns its keep: the hostname or server name in a multi-tenant deployment. Cookie bloat concentrates on a single parent domain, which is what a university IT team found when site-wide cookies scoped to a shared domain accumulated until users could not log in per this University of Minnesota how-to. Splitting your 431 metric by hostname turns a mystery into a short list of domains.

Common causes, with the numbers that matter

Cookies dominate. Every cookie set for a domain is sent on every request to that domain, so they accumulate rather than replace one another, and browsers cap a single cookie at 4 KB, so a handful of large cookies exhausts an 8 KB buffer per this 431 explainer. Three 3 KB cookies plus ordinary browser headers is enough. In practice the causes are:

  • An SSO layer storing identity claims in cookies, plus several applications sharing one domain.
  • A JWT stored in a cookie or a custom header, growing as claims are added. Base64 encoding inflates a payload by about 33 percent per the Base64 reference. A 1.5 KB JSON payload becomes roughly 2 KB encoded, and it rides on every request.
  • A chain of proxies each appending forwarded-for headers.
  • A tracking or analytics stack that appends query parameters to marketing URLs, which is a 414 cause rather than a 431 one.
  • A cursor-based API where the cursor is a base64-encoded sort key and unique id, and the client resends it in the URL on every page as this cursor pagination write-up describes. A cursor carrying a timestamp plus a large id is small, but one carrying several filters and a sort signature is not, and it grows with every filter you add.
  • Encoded redirects: a redirect that appends the original URL as a return parameter, repeated two or three times, produces a query string that doubles with each hop, which is the 414 pattern this guide calls out via the 414 guide.

A worked example makes the sizing concrete. Suppose a 4 KB cookie budget is shared across three apps: an identity cookie at 2.5 KB, a preferences cookie at 1 KB, and a banner-dismissal cookie at 400 bytes. Add a User-Agent and Accept headers of roughly 500 bytes, and the header block lands near 4.4 KB, under the 8 KB limit. Now the identity team adds an attribute and the identity cookie grows to 4 KB. The block crosses 6 KB, and any user with a second SSO cookie on the same parent domain crosses 8 KB. That user cannot load any page on any app under that domain until they clear cookies, and your 431 rate jumps without any deploy touching the failing service. The fix is to move claims to a server-side session and keep the cookie to an opaque identifier, not to raise the buffer to 16 KB and postpone the problem until the next attribute ships.

Alerts that page the right person

The goal is a page that arrives with a hypothesis attached. Split the metric by status code and by endpoint, because a 431 storm on the login path is an identity problem and a 414 storm on a search endpoint is an application problem.

SignalWindowThreshold basisWho gets paged
431 rate on a hostname5 minutesMore than 10x the 7-day same-hour baseline, minimum 5 requestsService owner for that hostname
414 rate on one endpoint5 minutesMore than 10x the 7-day same-hour baseline, minimum 5 requestsOwner of that endpoint
431 rate from one client fingerprint15 minutesAny sustained rate above baselineSupport queue, not on-call
414 from one source IP5 minutesAbove a fixed abuse thresholdSecurity queue
Rejection before reaching the app5 minutesAny nonzero rate on a route with zero historyPlatform team

Two rules keep this quiet. First, derive thresholds from your own traffic: take the 99th percentile of the per-endpoint 431 and 414 rate over the trailing week and alert at a multiple of it, with an absolute minimum so a quiet endpoint does not page on two requests. Second, route to the owner rather than to whoever is on call. A 431 caused by cookie bloat on a parent domain is a bug in the identity integration, not an infrastructure incident, and the person who can trim the cookie is the identity engineer.

Before you build the rule, know what your gateway will and will not pass through. Many API gateways expose the upstream status, so an nginx 400 arrives at your metrics pipeline as one number; if your gateway or CDN rewrites it, record the original status in a log field and alert on that. The Digital.ai documentation's advice to watch logs and metrics after any header-size change applies here too: if you raise a limit, expect the shape of the traffic to change and recalibrate per that documentation.

How you deliver the alert matters as much as the trigger. If your pages run through an incident tool that has an API, the notification payload is a place to put the hypothesis, the hostname, the top user agents and a link to the log query. If you are prototyping that delivery layer, or building an internal status page or alert triage console around it, you can describe it and have a specialist build it with you at Quanta.

Fixing each failure at the source

For a 431, the sequence is: confirm the cause from the cookie header, move bulky claims out of cookies into a server-side session keyed by an opaque token, audit cookies set on shared parent domains, and only then consider raising large_client_header_buffers (nginx) or LimitRequestFieldSize (Apache) as a stopgap. Raising the buffer stops users being locked out, but headers travel on every request, so a bloated header block is a permanent per-request cost per this 431 explainer. If you do raise it, step in 16 KB increments and watch the logs afterwards as Digital.ai recommends.

For a 414, the sequence is: find the endpoint from the log line, identify whether the URL is user-authored (a search or filter form), machine-generated (a redirect chain or a cursor), or adversarial. For user-authored URLs, cap the number of filter values and move long payloads to POST; the HTTP spec allows servers to reject long URIs, and the sound architectural answer is to stop putting form data in query strings per this guide. For cursors, keep the payload small. A cursor that carries a timestamp and an integer id encodes to a few dozen bytes; one that carries filter signatures and sort orders is larger. The reliability benefit of a stable keyset order is real, since sorting on a non-unique column alone can duplicate or skip items between pages as this write-up explains, but the cursor does not need to carry your whole filter object. Store the filter set server-side and let the cursor reference it.

There is a security dimension worth a line in your runbook. A 414 spike from a narrow set of addresses with absurdly long query strings is more likely an attempt to exhaust server resources than an integration bug, and the 414 guide names that pattern directly per the same guide. Keep that traffic out of the service-owner pager and in the security queue.

Treat the alert as a prompt to measure. Pull the top ten user agents, hostnames and endpoints for each code, and the answer is usually one line: one SSO rollout, one marketing redirect, one client that concatenates pagination parameters instead of replacing them. That last one is common in hand-rolled clients that append a growing cursor to a URL each request rather than building a fresh request per page, which is a client bug your API documentation and a test case can prevent.

FAQ

FAQ

Does nginx return a 431 for oversized headers?

Not usually. nginx answers the same condition with a 400 and the message "Request Header Or Cookie Too Large," so filter your dashboards on the message text or log the header byte count rather than relying on the status code alone per this 431 explainer. If you only monitor for 431, the nginx-fronted half of your traffic will look healthy while users are locked out.

What is the default header limit, and can I change it?

HTTP defines no limit; servers set their own. Apache defaults to 8 KB, IIS to 16 KB, and nginx caps the request line at 8 KB via large_client_header_buffers per this Stack Overflow answer and this 414 guide. You can raise each with a configuration directive, but trimming what clients send is better than a permanent buffer raise.

A 431 and a 414 both fire on the same endpoint. Which do I investigate first?

Start with the 414. The URL is generated by your client or a redirect chain, so the fix is usually inside your code and verifiable locally, while the 431 may depend on cookie state you cannot see. Compare the per-hostname rate to decide whether they are actually the same incident or two unrelated ones that happen to share a dashboard.

My API returns a cursor in the URL. Could that cause a 414?

Yes, if the cursor is large or the client appends parameters on each request instead of building a fresh request. Cursors are typically base64-encoded sort keys and ids per this write-up, and base64 adds about 33 percent to the payload per the Base64 reference, so keep the encoded value small. Return it in a Link header so clients reuse it unchanged rather than reconstructing it, as described in RFC 8288.

Can I just raise every limit and move on?

For a short period, yes, and it buys time. But headers and URLs travel on every request, so a bloated request is a permanent tax on latency, egress and log volume, and a larger buffer widens the surface for abuse. Fix the source first, raise the limit only as a documented stopgap, and re-check the logs afterwards, as Digital.ai's guidance suggests.

Sources

Sources

  1. API Pagination Guide: Cursor vs Offset vs Keyset for High ...
  2. Why Contentful is moving to cursor-based pagination
  3. 431 Request Header Fields Too Large - HTTP - MDN Web Docs
  4. 431 Request Header Fields Too Large: Causes & Fixes
  5. 414 Request-URI Too Long - HTTP Status Codes Guides
  6. difference between http header code 413 and 414
  7. Maximum on HTTP header values?
  8. Increase HTTP_MAX_HEADER_SIZE to 16kb · Issue #27645
  9. Configure maximum size of the HTTP request header
  10. Resolve Error 431: Request Header Fields Too Large on UMN ...
  11. Base64
  12. Systems Design - Pagination | TIL with Mohammad
  13. RFC 8288: Web Linking
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles