Quick answer
A 400 Bad Request error means a server rejected the request because something in it looked wrong: a malformed URL or header, an oversized or corrupt cookie, broken message framing, or plain HTTP sent to an HTTPS port. Any layer can send it, from the CDN to the reverse proxy to the application, and each words its page differently. Visitors can often fix it by clearing the site's cookies; owners should find which layer answered and read its log.
01What does error 400 Bad Request mean?
RFC 9110 defines 400 as the status a server sends when it "cannot or will not process the request due to something that is perceived to be a client error", and gives three examples: malformed request syntax, invalid message framing and deceptive request routing. It is the general-purpose 4xx code, and clients treat any 4xx they do not recognise as a 400.
Two consequences follow. Sending the same request again will fail again, because nothing about it has changed. And the code says little by itself: a 400 from a CDN about a broken URL and a 400 from an API about a missing JSON field have nothing in common except the number. The HTTP status code reference shows how 400 relates to the more specific codes such as 413, 414, 422 and 431.
02Causes of a 400, sorted by the layer that rejects them
Start by asking which layer refused the request, because each one checks different things.
| Layer | Typical causes | What you usually see |
|---|---|---|
| Browser or client | Corrupt or oversized cookies, a hand-edited URL with a stray % or space, an extension adding headers, a broken script building requests | Works in a private window or another browser |
| CDN or WAF edge | Invalid percent-encoding, header size limits, protocol violations, a custom rule that answers 400 | The provider's branded page or Server header |
| Reverse proxy or web server | Request header or cookie too large, missing or duplicate Host, invalid header line, Content-Length together with Transfer-Encoding, HTTP sent to the HTTPS port | A plain server page, such as nginx's |
| Application or API | Invalid JSON, missing or malformed parameters, OAuth invalid_request | A JSON or HTML error from the app itself |
03What the 400 page says on each server
The wording of the page is the quickest clue. The nginx texts below are its built-in pages, reproduced on nginx 1.22.1; the Node.js response was reproduced on Node.js 22, the Cloudflare page was observed, and the others are quoted from each project's source code or documentation.
| Server | Message on the page | What it means |
|---|---|---|
| nginx | 400 Bad Request, Request Header Or Cookie Too Large | One header line exceeded one large_client_header_buffers buffer (8 KB by default), or the headers exceeded all four together |
| nginx | 400 Bad Request, The plain HTTP request was sent to HTTPS port | http:// was sent to a TLS port, often a proxy or load balancer pointed at port 443 without TLS |
| nginx | 400 Bad Request, No required SSL certificate was sent | The server requires a client certificate (mutual TLS) and none was presented |
| nginx | 400 Bad Request, with nothing else | Anything else; the reason is only in the error log at info level |
| Apache httpd | Your browser sent a request that this server could not understand. Size of a request header field exceeds server limit. | A header exceeded LimitRequestFieldSize (8,190 bytes by default) |
| Apache httpd | The number of request header fields exceeds this server's limit. | More than LimitRequestFields (100 by default) |
| IIS (HTTP.sys) | HTTP 400 - Bad Request (Request header too long) | A header passed MaxFieldLength or MaxRequestBytes; the reason is logged in the HTTPERR log |
| Node.js | An empty body: just HTTP/1.1 400 Bad Request and Connection: close | The HTTP parser rejected the request (oversized headers get 431 instead) |
| Go net/http | 400 Bad Request: missing required Host header (or malformed Host header, invalid header name) | The reason is part of the response text |
| Cloudflare | An nginx-style 400 page with cloudflare in the footer and Server: cloudflare (observed October 2026) | Rejected at the edge before reaching the origin |
04The curl test that shows which layer rejected the request
Two comparisons answer most questions. First, send the request through the public hostname and straight to the origin with the same hostname and TLS server name: if only the first returns 400, the edge or a proxy rejected it, and the Server header says which. Second, if your browser gets a 400 and curl does not, the difference lies in what the browser sends.
For the second case, open the browser's developer tools, find the failing request in the Network panel, right-click it and choose Copy, then Copy as cURL. Paste the command into a terminal, confirm that it still fails, and delete headers one at a time, starting with Cookie, until it succeeds. The last header you removed is the cause.
# through the CDN or proxy
curl -s -o /dev/null -D - 'https://example.com/failing/path' | grep -iE '^(HTTP|server|via|cf-ray|x-cache)'
# straight to the origin, same Host header and SNI
curl -s -o /dev/null -D - --resolve example.com:443:203.0.113.10 'https://example.com/failing/path' | grep -iE '^(HTTP|server)'
# how many bytes the request itself was
curl -s -o /dev/null -w '%{http_code} %{size_request} bytes\n' 'https://example.com/failing/path'05Why nginx logs nothing for a 400, and how to see the reason
nginx writes the cause of a client-side 400 at the info level, and the error_log default level is error, so on a stock configuration the access log shows the 400 and the error log stays silent. Raise the level for a short while, reproduce the failure, then set it back, because info is verbose on a busy server.
Every line below was produced on nginx 1.22.1 by sending the matching broken request; long values are shortened.
| Error log line contains | Cause |
|---|---|
| client sent too long header line: "Cookie: ..." | A cookie or other header too large for the buffers |
| client sent too long URI | The request line exceeded one buffer; nginx answers 414, not 400 |
| client sent plain HTTP request to HTTPS port | http:// sent to a TLS listener |
| client sent HTTP/1.1 request without "Host" header | A hand-written or broken client |
| client sent duplicate host header | Two Host headers, a classic sign of a probe or a broken proxy |
| client sent invalid header line | A header name containing a space or a control character; names with other invalid characters, such as Bad@Header, are logged with the same text but ignored, not rejected |
| client sent "Content-Length" and "Transfer-Encoding" headers at the same time | Ambiguous framing, the raw material of request smuggling |
| client sent invalid request | Malformed percent-encoding such as %zz, or %00, in the URL |
# /etc/nginx/nginx.conf, main context
error_log /var/log/nginx/error.log info;
nginx -t && systemctl reload nginx
tail -f /var/log/nginx/error.log | grep 'client sent'06How to fix a 400 Bad Request as a visitor
- Check the address for stray characters: a % that is not followed by two hex digits, spaces, or a URL pasted twice
- Delete the cookies for that one site and reload; this fixes the cookie-size and corrupt-cookie cases
- Try a private window: if it works there, stored site data or an extension is the cause
- Disable browser extensions that rewrite pages or add headers, then retry
- If an upload fails, try a smaller file; some applications answer an oversized upload with 400 rather than 413
07How to fix a 400 as a site owner
Treat the 400 as a symptom and fix the component that produced the bad request. Raising limits or disabling checks only moves the problem.
- Oversized cookies: find the cookie that grew before raising nginx's large_client_header_buffers or Apache's LimitRequestFieldSize
- Plain HTTP to the HTTPS port: make the upstream scheme match, for example proxy_pass https:// for a TLS backend, or point the proxy at the HTTP port; the reverse mistake, plain HTTP answering on port 443, shows up in Chrome as ERR_SSL_PROTOCOL_ERROR
- Missing or wrong Host from a proxy or custom client: send the hostname the backend expects, for example proxy_set_header Host $host in nginx
- Ambiguous Content-Length and Transfer-Encoding: fix the client or library that sends both; do not relax the check, which protects against request smuggling
- API validation errors: return a body that names the field and the problem, for example in the Problem Details format of RFC 9457, so callers can fix their request
- A sudden spike in 400s from many addresses often means scanning or a flood of junk requests rather than a bug; a WAF absorbs that traffic before it reaches the origin
Questions
Frequently asked questions
Sources
Sources and verification
nginx 1.22.1 error pages and every error log line in the table were reproduced in an isolated network namespace with curl 7.88.1 and raw requests; the Node.js 22 parser responses were reproduced the same way; the Go messages are quoted from the net/http source; the curl commands were run with curl 7.88.1 against a public example host, which also returned the Cloudflare 400 page for a malformed URL. Debian 12, October 2026.
- 01RFC 9110, section 15.5.1: 400 Bad Request · RFC Editor
- 02Module ngx_http_core_module: large_client_header_buffers · nginx.org
- 03nginx source: ngx_http_special_response.c (built-in error pages) · nginx on GitHub
- 04Apache httpd source: server/protocol.c (header limit messages) · Apache on GitHub
- 05Apache core: LimitRequestFieldSize and LimitRequestFields · Apache HTTP Server
- 06HTTP 400 error responses to HTTP requests (Request header too long) · Microsoft Learn
- 07Types of errors logged by the HTTP Server API · Microsoft Learn
- 08Configuring HTTP Server API error logging · Microsoft Learn
- 09Node.js HTTP: Event 'clientError' · Node.js
- 10Go source: net/http server.go · Go on GitHub
- 11Chrome DevTools Network features reference (Copy as cURL) · Chrome for Developers
- 12RFC 9457, Problem Details for HTTP APIs · RFC Editor
Last updated