Skip to content
Control Plane Labs

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/search

What 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