< HTTP/1.1 400 Bad Request400 Bad Request
400 Bad Request means the server could not parse your request. Find which layer sent it, fix bad JSON and oversized cookies or headers, and reproduce with curl.
- Cacheable
- Only with explicit freshness
- Retry?
- No: fix the request
- Usually sent by
- Origin app, proxy or CDN
- Spec
- RFC 9110 §15.5.1
On this page
TL;DR: 400 Bad Request means the server, or a proxy in front of it, could not parse or accept the request as sent: broken JSON, an invalid header, an oversized cookie, or a body that fails validation. Find out which layer returned it, then replay the exact request with
curl -vand bisect.
RFC 9110 section 15.5.1 covers perceived client errors, including malformed syntax, invalid message framing and deceptive routing. Because it is a catch-all, the response body and the Server header tell you more than the status line.
The messages and response bodies here are illustrative. Client messages use https://api.example.com/resource; reason phrases and URLs can vary.
HTTP/1.1 400 Bad Request
Server: nginx
Content-Type: text/html
<html><head><title>400 Bad Request</title></head>
<body><center><h1>400 Bad Request</h1></center>
<center>Request Header Or Cookie Too Large</center>
<hr><center>nginx</center></body></html>
What you see in your client
- Axios:
AxiosError: Request failed with status code 400with the default status handling. Inspecterror.response.datafor the server’s explanation. Axios source. - fetch: the promise resolves with
response.status === 400andresponse.ok === false. Check the status before reading the body; HTTP errors do not entercatchautomatically. MDN fetch. - Python requests:
requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: https://api.example.com/resourcewhen you callresponse.raise_for_status(). Requests source. - curl -f:
curl: (22) The requested URL returned error: 400. Usecurl -iwithout-fwhile inspecting the error body. curl source. - .NET:
HttpRequestExceptionwith the English messageResponse status code does not indicate success: 400 (Bad Request).afterEnsureSuccessStatusCode(). Runtime message template. - Spring WebClient:
WebClientResponseException$BadRequest: 400 Bad Request from POST https://api.example.com/resourcewithretrieve(). ReadgetResponseBodyAsString(). Spring 6.2 source. - Spring RestTemplate (6.2):
HttpClientErrorException$BadRequest: 400 Bad Request on POST request for "https://api.example.com/resource": [no body]with the default error handler and an empty response body. Read the exception body when present. Message builder, exception subclasses.
Who sent it?
| Signal | Likely source |
|---|---|
HTML page with <center>nginx</center> and a reason line | nginx rejected it before your app ran |
Bad Request - Invalid Hostname or Bad Request - Invalid URL | IIS / HTTP.sys |
Bad Request: Your browser sent a request that this server could not understand. | Apache httpd |
CF-Ray header and a Cloudflare-styled page | Cloudflare edge |
Server: awselb/2.0 | AWS ALB (malformed request or header limits) |
| JSON body with field-level errors | Your application’s validation layer |
SyntaxError: Expected double-quoted property name in JSON at position 17 (older Node: Unexpected token } in JSON) | express.json() body parser |
nginx reason lines you will see in the page body, with the matching error-log text:
Request Header Or Cookie Too Large:large_client_header_buffers(default4 8k) was exceeded, usually by accumulated cookies. Log:client sent too long header lineorclient sent request with too large header.The plain HTTP request was sent to HTTPS port: someone spoke HTTP to alisten 443 sslport. Fix the scheme or port.No required SSL certificate was sent:ssl_verify_client onand the client presented no certificate.
Fix it, most common first
- Malformed JSON. A trailing comma, single quotes, comments, or an unescaped newline. Validate with
jq . < body.json. In shells, quoting is the usual culprit:-d '{"a":1}'works,-d "{"a":1}"does not. - Content-Type does not match the body. curl’s
-dsendsapplication/x-www-form-urlencoded. Form parsers can leave JSON fields missing and trigger validation; JSON-only endpoints can instead return 415. Add-H 'Content-Type: application/json'. - Cookies too big. Clear cookies for the domain. If it is your site, find what is writing large cookies: the combined
Cookieheader has to fit the server’s per-header buffer. - Invalid Host or URL characters. Unencoded spaces,
%not followed by two hex digits, or non-ASCII bytes in the request line. - Framing guards. Both
Content-LengthandTransfer-Encodingpresent, or conflicting duplicateContent-Lengthvalues. Servers reject or close these (RFC 9112 section 6.3) to block request smuggling; fix the client or the proxy that rewrites them. - Schema validation failure. Missing required field or wrong type. Many APIs use 422 for this instead.
Common causes by stack
- ASP.NET Core controllers:
[ApiController]returns 400 automatically for model validation failures, with anerrorsdictionary inValidationProblemDetails; read those keys before changing the serializer. Microsoft API documentation. - Django REST framework:
ParseErrorand serializerValidationErrorboth default to 400; distinguish broken JSON from a field validation message in the response body. DRF exceptions. - Rails:
ActionController::ParameterMissingandActionDispatch::Http::Parameters::ParseErrormap to 400; inspect the required parameter and send the expected JSON nesting. Rails exception mappings. - API Gateway REST APIs:
BAD_REQUEST_BODYandBAD_REQUEST_PARAMETERSdefault to 400 when request validation fails; compare the payload with the method’s model and required parameters. Gateway response types.
Fix by stack
nginx
http {
client_header_buffer_size 4k;
large_client_header_buffers 4 16k; # number and size; one header line must fit in one buffer
}
Express / Node.js
express.json() throws on invalid JSON, and the default error handler answers 400 with an HTML stack trace outside production. Return JSON instead:
app.use(express.json({ limit: '1mb' }))
app.use((err, req, res, next) => {
if (err.type === 'entity.parse.failed') {
return res.status(400).json({ error: 'invalid_json', message: err.message })
}
next(err)
})
Node’s HTTP parser tries to return 400 and close the socket for parse errors, but HPE_HEADER_OVERFLOW gets 431 by default. These errors reach clientError before your request handler; inspect err.code. Node HTTP documentation.
Spring and Django
Spring returns 400 for HttpMessageNotReadableException (JSON parse error: Unexpected character) and for MethodArgumentNotValidException when @Valid fails. Django turns SuspiciousOperation into 400; the classic one is Invalid HTTP_HOST header: 'x'. You may need to add 'x' to ALLOWED_HOSTS. with DEBUG = False.
Cloudflare and AWS
Cloudflare documents 400 for requests containing both Content-Length and Transfer-Encoding; inspect the framing headers added by each proxy. Cloudflare 400 guidance.
An ALB returns 400 for malformed requests, an incomplete body, or headers exceeding its documented limits: 16 K for a request line, 16 K for one header and 64 K for all request headers. Compare the browser’s cookies with a cookie-free request. ALB troubleshooting.
Reproduce and verify
# Bad JSON: a well-behaved parser answers 400
curl -sv -X POST https://api.example.com/users \
-H 'Content-Type: application/json' \
-d '{"name": "John",}' 2>&1 | grep -E '^(>|<) '
# Oversized cookie: nginx answers 400 "Request Header Or Cookie Too Large"
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/ \
-H "Cookie: junk=$(head -c 9000 /dev/zero | tr '\0' a)"
If the request works from curl but fails from the browser, diff the headers: the browser is sending cookies, Origin, or a body encoding that curl is not.
Write a useful 400 body
RFC 9457 application/problem+json is the standard shape for API errors:
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/invalid-json",
"title": "Request body is not valid JSON",
"status": 400,
"detail": "Unexpected token '}' at position 45"
}
Use 404 for missing resources, 401/403 for authentication or permission failures, and 5xx for server faults.
400 vs its neighbors
Use 400 when the request cannot be parsed (syntax or framing) and 422 Unprocessable Content when it parses but fails semantic validation. Many APIs return 400 for both; consistency matters more than the choice. A header block that is too large has its own code, 431, though nginx still uses 400 for it. See also 401, 403 and 404. You can trigger a 400 safely in the request builder.
See also the comparison 400 vs 422: which of the two to return for validation errors and what common frameworks default to.
Frequently asked questions
What does 400 Bad Request mean?
The server or a proxy in front of it could not parse or accept the request as sent, for example because of invalid JSON, a malformed header, an oversized cookie, or conflicting framing headers. RFC 9110 section 15.5.1 treats it as the general client-error status.
How do I fix 400 Bad Request: Request Header Or Cookie Too Large?
Clear the site's cookies in the browser. If you run the server, find the code setting the oversized cookies and, as a stopgap, raise large_client_header_buffers in nginx (for example 4 16k).
Why does my API return 400 for valid-looking JSON?
Compare Content-Type with the body: curl -d defaults to application/x-www-form-urlencoded. Validate the exact bytes with jq to catch shell quoting mistakes and trailing commas, then inspect field errors. ASP.NET Core model validation and DRF serializer validation can return 400 even when the JSON parses correctly.
Should I use 400 or 422 for validation errors?
Use 400 when the request cannot be parsed and 422 Unprocessable Content when it parses but fails semantic validation. Both are widely used for validation; choose one convention and apply it consistently across the API.
Can a 400 error be caused by the server?
Sometimes. A proxy or server misconfiguration, such as too-small header buffers, a rewrite that corrupts the request line, or a gateway that mangles Host, produces 400s for valid client requests. The Server header and error log identify the culprit.
Sources
Related
400 vs 422: Malformed vs Invalid Requests
400 Bad Request or 422 Unprocessable Content for validation errors? The syntax-vs-semantics rule, what Rails, Laravel, FastAPI and Spring return by default, and raw examples.
497 HTTP Request Sent to HTTPS Port (nginx)
nginx 497 means plain HTTP hit an HTTPS port; clients see "400 The plain HTTP request was sent to HTTPS port". Fix with error_page 497. Also 494, 495, 496.
463 Too Many Forwarded IP Addresses (AWS ALB)
AWS ALB 463 rejects an X-Forwarded-For header with more than 30 IP addresses. Trace proxy appends and correct the forwarding chain at a trusted ingress.
464 Incompatible Request Protocol (AWS ALB)
AWS ALB 464 means the incoming HTTP or gRPC request conflicts with the target group protocol version. Inspect ProtocolVersion and listener routing.
405 Method Not Allowed
405 Method Not Allowed means the URL exists but not for that method. Read the Allow header, then fix redirects turning POST into GET, static hosting and WAFs.