How to Fix ERR_HTTP2_PROTOCOL_ERROR in Chrome
One check for visitors, then the header and reset rules Chrome enforces, with fixes for the server you run.
Updated
TL;DR
ERR_HTTP2_PROTOCOL_ERROR means the site's server, or a CDN or proxy answering for it, sent an HTTP/2 response Chrome refuses or cut it off with a stream reset. If it fails on every device, only the site's owner can fix it, by correcting headers or the failure behind the reset.
What ERR_HTTP2_PROTOCOL_ERROR looks like in Chrome and other clients
Between heading and code, Chrome suggests the site may be down, although its server did answer.
| Where | What you see |
|---|---|
| Chrome error page, heading | This site can't be reached |
| Chrome error page, code under the summary | ERR_HTTP2_PROTOCOL_ERROR |
| Chrome DevTools Console, page or file cut off | Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR |
| Puppeteer page.goto() | net::ERR_HTTP2_PROTOCOL_ERROR at https://example.com |
| Playwright page.goto() | page.goto: net::ERR_HTTP2_PROTOCOL_ERROR at https://example.com/ |
| curl --http2, server reset the stream (exit code 92) | curl: (92) HTTP/2 stream 1 was not closed cleanly: PROTOCOL_ERROR (err 1) |
| Node.js http2 client | Error [ERR_HTTP2_STREAM_ERROR]: Stream closed with error code NGHTTP2_PROTOCOL_ERROR |
Why this happens
The site's server broke HTTP/2's rules in its reply, or abandoned the reply midway.
Chrome checks each HTTP/2 response against the protocol's rules and drops one that breaks them. It also shows this code for nearly every stream reset the server sends, whatever error code the reset carries.
Diagnose your ERR_HTTP2_PROTOCOL_ERROR first
The first check needs only a second device; the rest are for whoever runs the site.
Load the page on another device on the same Wi-Fi; if it fails there too, your browser and computer are not the cause.
If the page loads blank or broken, open the DevTools Console; this code next to a file means that file's response broke.
Run curl -sv --http2 --max-time 15 on the URL; 'Invalid HTTP header field was received' names the header that broke the reply.
If curl prints 'was not closed cleanly' plus an error name, the server abandoned the reply rather than sending a bad header.
Repeat that curl test with --http1.1; if it succeeds, the problem lies in how your server, CDN or proxy speaks HTTP/2.
Solutions ranked by effectiveness
Start with the headers, then the resets; the last fix buys time while you search.
- Most common fix
Send only headers Chrome accepts over HTTP/2
Applies when curl names a rejected header or every visitor fails on the same pages. Change the app code or proxy rule that produces the header, then reload those pages.
Use lowercase header names, such as x-request-id rather than X-Request-ID.
Drop transfer-encoding: chunked; HTTP/2 carries the body in DATA frames instead.
Remove line breaks and other control characters from header values.
Keep all response headers, Set-Cookie included, below Chrome's 256 KiB limit.
- Check next
Find out why the server resets streams
Applies when curl reports a stream that was not closed cleanly. The server gave up on the reply, so find what stopped it.
On nginx, set the error_log level to info; the default level, error, skips client timeouts.
Reproduce the failure and note its exact time.
Fix what each layer's error log, CDN to app, records at that moment.
- If nothing else works
Serve HTTP/1.1 until HTTP/2 is fixed
A stopgap while the cause stays hidden. Chrome speaks HTTP/2 over TLS only when the server picks h2 in the handshake, so turning that off moves visitors to HTTP/1.1.
In nginx, delete the http2 parameter from every listen line on that port.
On nginx 1.25.1 or later, also set http2 off; in that server block.
Reload, then confirm that curl -sI --http2 prints an HTTP/1.1 status line.
Stop the ERR_HTTP2_PROTOCOL_ERROR from coming back
Checks that catch a broken HTTP/2 reply before many visitors meet it.
Run curl --http2 --max-time 15 on key pages after each release or CDN change; fail on any non-zero exit.
Run headless Chrome in that check too; in our test, curl and Node.js accepted a header line break Chrome rejected.
Collect failures from real visitors: a NEL policy with a Report-To group makes Chrome send h2.protocol.error reports.
Related errors
Learn more
FAQ
Many requests, one proxy port
Geonode's docs say thousands of requests can share one port, with no slowdown or conflicts.


