net::ERR_INVALID_RESPONSE (tested)

Chrome shows net::ERR_INVALID_RESPONSE or net::ERR_INVALID_HTTP_RESPONSE when it cannot make sense of what the server sent. We wrote a raw TCP server that sends broken responses on purpose and recorded the exact error each client gave. Most broken responses get a more specific error code, and many load without any error at all. If the server sends nothing at all, see ERR_EMPTY_RESPONSE.

1. The short answer

In our tests, Chromium 153 never returned ERR_INVALID_RESPONSE for a malformed HTTP/1.1 response. When the status line or header block was unreadable it returned ERR_INVALID_HTTP_RESPONSE. Other faults got their own codes: ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH, ERR_INVALID_CHUNKED_ENCODING, ERR_CONTENT_DECODING_FAILED, ERR_CONTENT_LENGTH_MISMATCH and others.

So if you see either name, do not guess. Look at the exact code in DevTools (Network tab, or the console line Failed to load resource: net::...), then find it in the table below. For fetch() the page only gets TypeError: Failed to fetch; the real code is only in the console. See TypeError: Failed to fetch.

2. Our test setup

A Node net.createServer read each request and wrote a fixed string back, then closed the socket. No HTTP library touched the response, so we could send anything:

import net from 'net';
const C = {
  dupcl: 'HTTP/1.1 200 OK\r\nContent-Length: 2\r\nContent-Length: 3\r\n' +
         'Connection: close\r\n\r\nhi!',
  nulvalue: 'HTTP/1.1 200 OK\r\nX-Test: a\0b\r\nContent-Length: 2\r\n\r\nhi',
  // ... 60 cases
};
net.createServer(s => s.on('data', d => {
  const path = String(d).split(' ')[1].slice(1);
  s.end(C[path]);
})).listen(8992, '127.0.0.1');

Chromium loaded each URL as a top-level navigation and as a same-origin fetch(). Both gave the same net error in every failing case. Then we ran the same cases through curl, Node fetch and Python urllib.request.

3. Chromium 153 results

What the server sentChromium 153 result
No bytes, then closenet::ERR_EMPTY_RESPONSE
Garbage instead of a status line (HELLO WORLD)net::ERR_INVALID_HTTP_RESPONSE
Body with no status line at all (HTTP/0.9 style)net::ERR_INVALID_HTTP_RESPONSE
Text before the status line (Warning: something then HTTP/1.1 200 OK)net::ERR_INVALID_HTTP_RESPONSE
NUL byte in a header value or header namenet::ERR_INVALID_HTTP_RESPONSE
101 Switching Protocols to a normal requestnet::ERR_INVALID_HTTP_RESPONSE
103 Early Hints followed by garbagenet::ERR_INVALID_HTTP_RESPONSE
Two Content-Length headers, different values (2 and 3)net::ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH
Two Content-Disposition headers, different valuesnet::ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION
302 with two different Location headersnet::ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION
302 with Location: http://[badnet::ERR_INVALID_REDIRECT
One header of 300,000 bytesnet::ERR_RESPONSE_HEADERS_TOO_BIG
Chunk size zz, or a 20-digit hex chunk sizenet::ERR_INVALID_CHUNKED_ENCODING
Chunked body cut off before the 0 chunknet::ERR_INCOMPLETE_CHUNKED_ENCODING
Content-Length: 50, only 2 bytes sentnet::ERR_CONTENT_LENGTH_MISMATCH
Content-Encoding: gzip (or br, deflate, gzip, gzip) on a plain-text bodynet::ERR_CONTENT_DECODING_FAILED
407 from a server that is not a proxynet::ERR_UNEXPECTED_PROXY_AUTH
Only 100 Continue, then closenet::ERR_EMPTY_RESPONSE

For the body errors (chunked, length, decoding) the navigation committed with status 200 and then failed. The headless page stayed blank and the load event never fired. fetch() rejected.

4. Broken responses Chromium accepted without error

These all loaded with no error in Chromium 153. Other clients were stricter (section 5).

A 204 or 304 that carries a body made the navigation end in net::ERR_ABORTED (no new page). fetch() returned 204 or 304 with an empty body.

5. Same cases in curl, Node fetch and Python

Casecurl 8.14.1Node 24 fetch (error.cause)Python 3.13 urllib
Garbage status lineexit 1, "Received HTTP/0.9 when not allowed""Response does not match the HTTP/1.1 protocol (Expected HTTP/, RTSP/ or ICE/)"BadStatusLine
Text before status lineexit 1, same HTTP/0.9 messagesame as aboveBadStatusLine
HTTP/1.1 abc OKexit 1, "Unsupported HTTP/1 subversion in response""(Invalid status code)"BadStatusLine
Status 1000exit 8, "Invalid status line""(Invalid response status)"BadStatusLine
HTTP/2.0 status lineOK 200OK 200UnknownProtocol: HTTP/2.0
Space before colonOK 200"(Invalid header token)"OK 200
Header line with no colonexit 8, "Header without colon""(Invalid header token)"OK 200
NUL in header valueexit 8, "Nul byte in header""(Invalid header value char)"OK 200
Bare CR in header valueOK 200"(Missing expected LF after header value)"OK 200
LF-only line endingsOK 200"(Missing expected CR after response line)"OK 200
Obs-fold headerOK 200"(Unexpected whitespace after header value)"OK 200
Two Content-Length, 2 and 3OK 200, read 3 bytesUND_ERR_RES_CONTENT_LENGTH_MISMATCHOK 200, read 2 bytes
Two Content-Length, both 2OK 200UND_ERR_RES_CONTENT_LENGTH_MISMATCHOK 200
Content-Length: abcexit 8, "Invalid Content-Length: value""(Invalid character in Content-Length)"OK 200
Content-Length + chunkedOK 200UND_ERR_RES_CONTENT_LENGTH_MISMATCHOK 200
Chunk size zzexit 56, "chunk hex-length char not a hex digit: 0x7a""terminated", cause "(Invalid character in chunk size)"IncompleteRead(0 bytes read)
Fake gzip bodywith --compressed: exit 61, "Error while processing content unencoding: incorrect header check""terminated", cause Z_DATA_ERROR "incorrect header check"OK 200, raw bytes (urllib does not decode)
101 to a normal GETOK, code 101ERR_ASSERTION "assert(upgrade)"HTTPError 101
300 KB headerexit 100, "A value or data field grew larger than allowed"UND_ERR_HEADERS_OVERFLOWLineTooLong (65536 bytes)

The outer Node error is always TypeError: fetch failed (or terminated if the body broke). Print err.cause to see the real reason.

6. HEAD response with a body

We answered a HEAD request with Content-Length: 2 plus the 2 body bytes, and left the connection open. Chromium fetch, Node fetch and curl all returned 200 with an empty body and no error. None of them reused that connection: the next GET arrived on a new TCP connection (curl -v printed "shutting down connection #0"). So a stray HEAD body does not break clients we tested, but it costs a new connection every time. Fix it anyway: never write a body for HEAD.

7. Fixes

If the connection is cut rather than malformed, see ERR_CONNECTION_RESET. For HTTP/2 framing errors, see ERR_HTTP2_PROTOCOL_ERROR.

Frequently asked questions

What is the difference between ERR_INVALID_RESPONSE and ERR_INVALID_HTTP_RESPONSE?

Both are Chromium network errors. In our tests with Chromium 153, every unreadable HTTP/1.1 status line or header block (garbage, text before the status line, NUL bytes in headers, a stray 101) gave ERR_INVALID_HTTP_RESPONSE. None of our 60 malformed responses produced ERR_INVALID_RESPONSE.

Does a space before the colon in a header break Chrome?

Not in Chromium 153: X-Test : a loaded fine, and so did curl and Python. Node 24 fetch rejected it with 'Invalid header token'. Fix it anyway, since stricter clients fail.

What happens with two Content-Length headers?

If the values differ, Chromium fails with net::ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH. If they are equal, Chromium loads the page, but Node 24 fetch still failed with UND_ERR_RES_CONTENT_LENGTH_MISMATCH.

Does Content-Encoding: gzip with a plain body give ERR_INVALID_RESPONSE?

No. Chromium 153 gave net::ERR_CONTENT_DECODING_FAILED, curl --compressed exited 61 with 'incorrect header check', and Node fetch failed with Z_DATA_ERROR.

How do I see what the server really sent?

Run curl -v against the URL. It prints the status line and headers it received and names the problem, for example 'Nul byte in header' or 'Received HTTP/0.9 when not allowed'. In Chrome, read the net:: code in the DevTools console.

Written with AI assistance. Measured on 2026-10-02 with headless Chromium 153.0.8010.12 (Playwright, navigation and fetch), Node.js 24.21.0 fetch, curl 8.14.1 and Python 3.13.5 urllib, against a raw Node net server on 127.0.0.1 that wrote 60 hand-crafted HTTP/1.1 responses byte by byte. PlainHeaders is our extension; curl, Python, Node.js and Chromium are free.