414 URI Too Long
Client error response defined by RFC 9110 §15.5.15.
Last updated July 27, 2026
Common causes at a glance
- A GET carrying serialised filter, sort, or selection state that grew unbounded
- A form submitted with GET instead of POST so every field lands in the query string
- A redirect loop appending a return-to parameter on each hop
- nginx large_client_header_buffers too small for the generated request line
Reproduce it
curl -i -G --data-urlencode "q=$(head -c 20000 /dev/zero | tr '\0' 'a')" https://api.example.com/v1/searchWhat 414 URI Too Long means
URI length has no limit in the specification, but every implementation imposes one because the request line has to land in a fixed-size buffer before anything can parse it. That buffer is typically a few kilobytes. Cross it and the server gives up on the request line itself, long before routing, authentication, or your handler.
In practice 414 is a query string problem, not a path problem. Nobody writes an
8 KB path; plenty of applications serialise a whole filter state or a list of
selected IDs into ? parameters and watch it grow with the dataset. The
request works fine in testing with three filters selected and fails for the customer
who selected four hundred.
What the spec says
RFC 9110 §15.5.15 describes the condition as rare and
names three origins: a client improperly converting a POST to a
GET with long query information, a redirect loop where each hop appends to
the URI, and an attack probing for buffer handling bugs. It also notes that a 414
response is heuristically cacheable. The request-target syntax is
RFC 9112 §3.2. Nothing in the protocol bounds URI length, so every
limit you hit is an implementation’s buffer, not a standard.
What actually causes it
The archetype is a search or reporting UI that keeps its whole state in
the URL so pages are shareable, then hands the user a multi-select. Two hundred
selected IDs at roughly forty characters each blows past 8 KB without anything
looking wrong in code review. Signed URLs and OAuth flows contribute too: a JWT in a
query parameter is easily a kilobyte, and a nested redirect_uri containing
its own encoded redirect_uri doubles on each round trip. On the server
side, nginx’s
large_client_header_buffers
defaults to 4 buffers of 8 KB, and the request line must fit in one of them —
raising the count does not help a single long line.
How to debug it
Measure before you tune anything. Print the URL your client actually builds and count the bytes, then bisect against the server:
for n in 2000 4000 8000 16000; do
q=$(head -c "$n" /dev/zero | tr '\0' 'a')
printf '%s -> ' "$n"
curl -s -o /dev/null -w '%{http_code}\n' -G --data-urlencode "q=$q" https://api.example.com/v1/search
done
The size where the code flips from 200 to 414 identifies the buffer. A boundary near
8 KB is nginx; a CDN may cap lower than the origin, so test both. Raising the
limit buys headroom but not a fix. The durable answer is to move the payload into a
request body with POST, or to persist the filter state server-side and
pass a short opaque key in the URL.
Working examples
curl
# Bisect the limit rather than guessing at buffer sizes.
for n in 2000 4000 8000 16000; do
q=$(head -c "$n" /dev/zero | tr '\0' 'a')
printf '%s -> ' "$n"
curl -s -o /dev/null -w '%{http_code}\n' \
-G --data-urlencode "q=$q" \
https://api.example.com/v1/search
done
Python (requests)
import requests
url = "https://api.example.com/v1/search"
for n in (2_000, 4_000, 8_000, 16_000):
prepared = requests.Request("GET", url, params={"q": "a" * n}).prepare()
resp = requests.Session().send(prepared, timeout=15)
print(n, len(prepared.url), resp.status_code)
# The fix: same query, sent as a body.
print(requests.post(url, json={"q": "a" * 16_000}, timeout=15).status_code)
Node (fetch)
const base = "https://api.example.com/v1/search";
for (const n of [2000, 4000, 8000, 16000]) {
const url = new URL(base);
url.searchParams.set("q", "a".repeat(n));
const res = await fetch(url);
console.log(n, url.toString().length, res.status);
}
// Move the oversized parameter into a POST body.
const res = await fetch(base, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ q: "a".repeat(16000) }),
});
console.log(res.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
- 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
- 494 Request Header Too Large
- 495 SSL Certificate Error
- 496 SSL Certificate Required
- 497 HTTP Request Sent to HTTPS Port
- 499 Client Closed Request