400 Bad Request (tested)

400 Bad Request means the server could not parse or accept the request itself. It is the generic "your request is broken" code. We sent broken requests to real servers and wrote down exactly what came back.

1. What a 400 means

The server got your request but refused it because something in it was malformed. It is a client-side error: retrying the same request gives the same 400. The fix is to change the request.

A 400 can come from two layers. The HTTP parser rejects bytes it cannot read, before your code runs. The framework rejects a body it cannot decode, such as broken JSON. Validation errors in your own code often use 422 Unprocessable Entity instead.

2. Parser-level 400s in Node.js 24 (measured)

We sent raw bytes to a plain http.createServer() on Node.js 24.21.0. For every broken request below, Node replied with exactly this and closed the connection. There is no body, and your request handler never runs.

HTTP/1.1 400 Bad Request
Connection: close
What we sentNode.js 24 result
GARBAGE as the request line400, connection closed
Header name with a space: Bad Header: v400, connection closed
Control byte (0x01) in a header value400, connection closed
Space in the path: GET /a b HTTP/1.1400, connection closed
DEL byte (0x7F) in the path400, connection closed
Two different Content-Length headers (5 and 6)400, connection closed
HTTP/1.1 with no Host header400 (with Date and an empty chunked body)
HTTP/1.1, no Host, server option requireHostHeader: false200 OK
HTTP/1.0 with no Host header200 OK

So Node.js 24 does reject HTTP/1.1 requests without Host by default. You can turn that off with http.createServer({ requireHostHeader: false }), but real clients always send Host, so a missing one usually means a broken hand-written client.

Headers that are too large get a different code. See 431 Request Header Fields Too Large.

3. Malformed JSON in Express (measured)

With app.use(express.json()) on Express 5.2.1, we posted {"a":1,} (trailing comma) with Content-Type: application/json. The parser raised an error with status 400. Our error handler printed its fields:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8

{"type":"entity.parse.failed","message":"Expected double-quoted property name in JSON at position 7 (line 1 column 8)","status":400}

Check err.type === 'entity.parse.failed' in your error handler to return a clean message instead of a stack trace.

4. Malformed JSON in Flask (measured)

On Flask 3.1.3, reading request.json on the same broken body returned:

400 BAD REQUEST
<title>400 Bad Request</title>
<h1>Bad Request</h1>
<p>The browser (or proxy) sent a request that this server could not understand.</p>

The page does not say the JSON was the problem. Two more results from the same run:

5. What clients do before the server sees anything (measured)

Client and inputResult
curl 8.14.1, URL http://localhost:8986/a bNot sent. curl: (3) URL rejected: Malformed input to a URL function, exit code 3
curl, URL with %20 instead of the spaceSent, server saw /a%20b, 200
curl, header value with a 0x01 byteSent as-is; Node replied 400
Node 24 fetch(), header value with a newlineTypeError: Headers.append: "a\nb" is an invalid header value.
Node 24 fetch(), header name Bad HeaderTypeError: Headers.append: "Bad Header" is an invalid header name.
Node 24 fetch(), header value with ✓TypeError: Cannot convert argument to a ByteString because the character at index 5 has a value of 10003 which is greater than 255.
Node 24 fetch(), URL with a spaceEncoded to /a%20b and sent, 200

So curl and fetch() catch many mistakes on your side. Raw sockets, old libraries and hand-built requests do not, and the server answers 400.

6. 400s you see in the browser (from documentation, not measured)

7. Quick fix checklist

  1. Browser: clear cookies for the site, then reload. Try a private window.
  2. Check the URL for spaces, stray % signs or pasted junk.
  3. API client: validate your JSON (a trailing comma is enough to fail) and send Content-Type: application/json.
  4. Check header names and values for spaces, newlines or non-ASCII characters.
  5. Hand-built HTTP/1.1 requests: always send Host and one Content-Length.

Frequently asked questions

Is 400 Bad Request my fault or the server's?

Usually the request is the problem. The server could not parse it or decode its body. Retrying the same request will fail again; change the request.

Does Node.js return 400 when the Host header is missing?

Yes, in our test on Node.js 24.21.0 an HTTP/1.1 request with no Host got 400. HTTP/1.0 without Host got 200, and the server option requireHostHeader: false turns the check off.

Why does Flask return 400 for my JSON?

request.json raises a 400 when the body is not valid JSON. In our test the page only said 'The browser (or proxy) sent a request that this server could not understand.' Use request.get_json(silent=True) to handle it yourself.

What is entity.parse.failed in Express?

It is the error type express.json() sets when the body is not valid JSON. The error has status 400 and a message such as 'Expected double-quoted property name in JSON at position 7'.

How do I fix a 400 error in my browser?

Clear cookies for that site and reload, or try a private window. Also check the URL for spaces or stray % signs. Oversized cookies are a common cause on nginx sites.

Written with AI assistance. Measured on 2026-10-02 with Node.js 24.21.0 (http module), Express 5.2.1, Flask 3.1.3 / Werkzeug 3.1.9 and curl 8.14.1. Raw requests were sent with a Python socket so nothing fixed them on the way. The nginx, Google and cookie notes come from documentation, not our runs. PlainHeaders is our extension; curl, Node.js, Express and Flask are free.