How HTTP Works
4xx · Client error
< HTTP/1.1 400 Bad Request

400 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.

Reviewed 6 min readbeginner5 sourcesTry itMarkdown
Cacheable
Only with explicit freshness
Retry?
No: fix the request
Usually sent by
Origin app, proxy or CDN
Spec
RFC 9110 §15.5.1
Often confused with
422, 409
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 -v and 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 400 with the default status handling. Inspect error.response.data for the server’s explanation. Axios source.
  • fetch: the promise resolves with response.status === 400 and response.ok === false. Check the status before reading the body; HTTP errors do not enter catch automatically. MDN fetch.
  • Python requests: requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: https://api.example.com/resource when you call response.raise_for_status(). Requests source.
  • curl -f: curl: (22) The requested URL returned error: 400. Use curl -i without -f while inspecting the error body. curl source.
  • .NET: HttpRequestException with the English message Response status code does not indicate success: 400 (Bad Request). after EnsureSuccessStatusCode(). Runtime message template.
  • Spring WebClient: WebClientResponseException$BadRequest: 400 Bad Request from POST https://api.example.com/resource with retrieve(). Read getResponseBodyAsString(). 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?

SignalLikely source
HTML page with <center>nginx</center> and a reason linenginx rejected it before your app ran
Bad Request - Invalid Hostname or Bad Request - Invalid URLIIS / HTTP.sys
Bad Request: Your browser sent a request that this server could not understand.Apache httpd
CF-Ray header and a Cloudflare-styled pageCloudflare edge
Server: awselb/2.0AWS ALB (malformed request or header limits)
JSON body with field-level errorsYour 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 (default 4 8k) was exceeded, usually by accumulated cookies. Log: client sent too long header line or client sent request with too large header.
  • The plain HTTP request was sent to HTTPS port: someone spoke HTTP to a listen 443 ssl port. Fix the scheme or port.
  • No required SSL certificate was sent: ssl_verify_client on and the client presented no certificate.

Fix it, most common first

  1. 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.
  2. Content-Type does not match the body. curl’s -d sends application/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'.
  3. Cookies too big. Clear cookies for the domain. If it is your site, find what is writing large cookies: the combined Cookie header has to fit the server’s per-header buffer.
  4. Invalid Host or URL characters. Unencoded spaces, % not followed by two hex digits, or non-ASCII bytes in the request line.
  5. Framing guards. Both Content-Length and Transfer-Encoding present, or conflicting duplicate Content-Length values. Servers reject or close these (RFC 9112 section 6.3) to block request smuggling; fix the client or the proxy that rewrites them.
  6. 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 an errors dictionary in ValidationProblemDetails; read those keys before changing the serializer. Microsoft API documentation.
  • Django REST framework: ParseError and serializer ValidationError both default to 400; distinguish broken JSON from a field validation message in the response body. DRF exceptions.
  • Rails: ActionController::ParameterMissing and ActionDispatch::Http::Parameters::ParseError map to 400; inspect the required parameter and send the expected JSON nesting. Rails exception mappings.
  • API Gateway REST APIs: BAD_REQUEST_BODY and BAD_REQUEST_PARAMETERS default 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

  1. MDN Web Docs: 400 Bad Requestdeveloper.mozilla.org
  2. RFC 9110 section 15.5.1: 400 Bad Requestrfc-editor.org
  3. RFC 9112 section 6.3: Message Body Lengthrfc-editor.org
  4. RFC 9457: Problem Details for HTTP APIsrfc-editor.org
  5. nginx: large_client_header_buffersnginx.org

Keep going

Browse /search