400
The server refused to interpret the request at all — something in the request line, a header, the framing or the body is malformed or oversized — which usually means none of your application code ever ran.
The same three digits are three different problems depending on which side of the request you are on. Read the block that describes you.
Why you are seeing it
It is worth knowing what you cannot cause. The `Headers` constructor rejects invalid header names and values before anything is sent, so a browser will not emit a syntactically broken header — which leaves two realistic shapes. The first is size: a cookie jar that keeps growing until one header line exceeds the server's buffer, at which point nginx answers 400 with a page reading "Request Header Or Cookie Too Large". The second is the URL: a raw space, `{`, `}` or `|` interpolated straight into a template string reaches the wire as-is and some servers refuse the request line outright. There is also a misreading worth heading off — if the CORS preflight `OPTIONS` is what returns 400, the browser reports a CORS failure, and you will spend the afternoon on `Access-Control-Allow-Origin` headers for a request that was rejected before CORS was ever considered.
What to do
Right-click the failing row in the network panel, copy it as cURL, and run it in a terminal. If it fails there too, the request itself is malformed and the browser is not part of the story — which immediately halves the search. Then bisect: reload in a private window, or delete the site's cookies, and see whether the 400 disappears; that one test separates "too big" from "malformed" without reading any server config. Encode anything you interpolate into a URL with `encodeURIComponent` for path and query fragments rather than trusting the input. And read the response body before concluding anything: a protocol-level 400 comes back as the server's own HTML error page, while your API's validation error comes back as your JSON, and that is the difference between "the request never reached the application" and "the application said no".
Why you are seeing it
Start by finding out whether the request reached you at all, because the two 400s have different owners. nginx's error log names its causes directly: `client sent too long header line` is the oversized-cookie case, and `client sent invalid method while reading client request line` is a genuinely malformed request. The size limits are named in nginx's own directives — `client_header_buffer_size` sets the first buffer and `large_client_header_buffers` sets the rest, and nginx's documentation is explicit that an over-long *request line* yields 414 while an over-long *header* yields 400. One more nginx-specific 400 catches people every time: plaintext HTTP sent to a TLS listener produces nginx's internal 497, which reaches the client as a 400 whose body reads "The plain HTTP request was sent to HTTPS port" — so a 400 that appears the moment someone drops the `s` from `https` is a port pairing, not a payload. Strict framing checks in modern servers add the rest: duplicate `Content-Length`, whitespace before a colon, or bare LF line endings from a hand-rolled client or an ageing proxy.
What to do
Grep the application log for the request id or the path first. No line means the rejection happened in front of your code, and the proxy's error log is the only place the reason exists. If it is the buffer, raise it (`large_client_header_buffers 4 16k`) but treat that as buying time — a cookie that grows without bound is its own bug, and the next size will arrive. Reproduce at the wire with `curl -v` and add headers back one at a time until it breaks, which names the offending header rather than the offending request. If the request is being generated by your own code, check the framing fields: never set `Content-Length` alongside `Transfer-Encoding`, and let the client library compute the length. And when the 400 is genuinely yours, put the failing field in the response body and ask whether 422 is the truer code — RFC 9110 §15.5.21 exists for content that parsed cleanly and still cannot be acted on.
Why you are seeing it
A 400 almost always means something your browser sent alongside the address was rejected, rather than the page being missing or the site being down. The two everyday causes are a cookie for that site that has grown too large or become corrupted — cookies are sent with every request, and a server will refuse a request whose headers are bigger than it allows — and an address that got mangled between where you copied it and where you pasted it, typically by a line break in an email or a stray space from a chat message.
What to do
Clear that one site's cookies and reload; in Chrome that is the padlock or tune icon in the address bar, then "Cookies and site data", then delete. This fixes the majority of 400s people meet and, unlike clearing everything, it signs you out of nothing else. If that does not do it, look hard at the address for a space, a line break or a missing character, and retype the site's home address rather than reusing the pasted link. A private window is the fast confirmation: it carries none of your cookies, so a page that loads there and fails in your normal window has told you exactly which of the two causes it is.
Run this against the URL that failed. It prints the status without the error page, so you can see what the server said rather than what the browser rendered.
curl -sS -D - -o /dev/null -H "Cookie: pad=$(head -c 20000 /dev/zero | tr '\0' 'a')" https://example.com/This sends one enormous `Cookie` header — 20 000 bytes, comfortably past the 8k that nginx's `large_client_header_buffers` allows by default — so against anything running nginx's defaults it reproduces the most common 400 on the public web deliberately rather than waiting for it. `-D -` prints the response headers, `-o /dev/null` discards the error page, and the status line is the result. Three readings follow. Drop the `-H` and re-run: a 200 proves the request shape is fine and only the size was wrong, which is a buffer question and not a syntax one. Halve the byte count until the 400 turns into a 200 and you have measured the server's actual header limit, which is the number to compare against `large_client_header_buffers`. If that first run comes back 200 instead, nothing is wrong with the command — the limit in front of you is simply larger than what you sent, which several edges are: double the padding to 40 000 and keep doubling until the status flips, because every reading here assumes you have actually seen the 400. And read `server:` and any `cf-ray` in the dump to learn who refused it, because an edge and an origin can have different limits and only one of them is in your config. For the other big family, run `curl -v http://example.com:443/`, which sends plaintext HTTP to the TLS port: nginx answers that with a 400 whose body reads "The plain HTTP request was sent to HTTPS port", and recognising that sentence saves an hour of reading application logs that contain nothing.
RFC 9110 §15.5.1 defines 400 as the server not processing the request due to something perceived to be a client error, and names three examples: malformed request syntax, invalid request message framing, and deceptive request routing. That single sentence covers two failures that live in different buildings, and telling them apart is most of the work. A protocol-level 400 is written by the server or the edge in front of it before any handler is dispatched — RFC 9110 §7.2 requires a server to answer 400 to any HTTP/1.1 request that lacks a `Host` field, carries more than one, or carries an invalid one, and RFC 9112 §6.3 makes a message carrying both `Content-Length` and `Transfer-Encoding` an error tied to request smuggling, which hardened servers reject rather than guess at. An application-level 400 is your own handler rejecting a body it successfully parsed. The first leaves no line in your application log; the second is a line in it, and that asymmetry is the fastest test available. RFC 9110 also offers a sharper code for the second case: §15.5.21 defines 422 (Unprocessable Content) for content whose syntax is correct but whose instructions cannot be followed, which is what most "validation failed" responses actually are.
| Confused with | How to tell them apart |
|---|---|
422 | 400 is for a request the server could not interpret; 422 (Unprocessable Content, RFC 9110 §15.5.21) is for content it interpreted perfectly and still cannot act on. A JSON body that fails your schema parsed fine — it is semantics, not syntax — so 422 tells the client the payload arrived intact and the values are wrong, which is a different fix from "your request was malformed". |
414 | Both are size limits and they sit either side of the first line break. nginx documents that a request *line* longer than the buffer returns 414 (URI Too Long, §15.5.15), while a header *field line* that is too long returns 400. So a 400 points at cookies and headers, and a 414 points at a query string somebody built in a loop — the code alone tells you which half of the request to shrink. |
431 | 431 (Request Header Fields Too Large, RFC 6585 §5) is the code that exists precisely for the oversized-cookie case, and it is far more informative than 400. nginx does not use it — it answers 400 with a "Request Header Or Cookie Too Large" body — so the absence of a 431 on the public web says nothing about whether headers are the problem. |
nginx | nginx writes the actual reason to its error log before it writes 400 to the client, and the messages are specific where the status code is not: `client sent too long header line` is size, `client sent invalid method while reading client request line` is syntax, and internal code 497 — plaintext sent to a TLS port — surfaces to the client as a 400 with an unmistakable body. One log line replaces an afternoon of guessing. |
All of these run in your browser — nothing is uploaded.
Codes people usually end up reading in the same session as 400.
Read the response headers rather than the page. curl -sS -o /dev/null -D - https://example.com/path prints them without the body, and server, via and cf-ray between them name the layer that answered. A cf-ray value means Cloudflare handled the response and is the id their support will ask for. To take the edge out of the picture entirely, repeat the request with --resolve example.com:443:203.0.113.10, which connects to the origin address you name while still sending the original host name and SNI — if the answer changes, the edge and the origin disagree.
No, and you should never branch on it. RFC 9112 tells clients to ignore the reason phrase, servers are free to change it, and HTTP/2 and HTTP/3 do not carry one at all — so 404 Not Found over HTTP/1.1 arrives as a bare 404 over HTTP/2. The phrases on this site are the registered ones because they are what people search for and what appears in an HTTP/1.1 log, not because any software depends on them.
Retry 5xx and 429; do not retry 4xx, because nothing about the request will be different next time. If the response carries Retry-After, honour it — RFC 9110 defines it for exactly this, and it may be either a number of seconds or a date. Otherwise use exponential backoff with jitter and a hard cap, and only for idempotent methods: a retried POST can charge a card twice. A retry storm against a server that is already failing is how a brief incident becomes a long one.
Yes, and the only rule that matters is that the code has to be true. Anything that reads your responses automatically — search engines, monitoring, caches, client retry logic — makes decisions from the number and never from the page. Serving an error page with 200 hides the failure from your own alerting; serving 200 for a missing page gets the error indexed as content; returning 500 for a request that was simply malformed sends whoever is on call to the wrong half of the stack.
Still stuck on 400? browse every status code in the reference — or go back up to the block written for your side of the stack.