August 28, 2017 (updated December 6, 2018)

When migrating your site to a more performant HTTP/2 protocol, it may happen that Chrome will not load a page and will display This site can’t be reached with ERR_SPDY_PROTOCOL_ERROR instead. HTTP/2 is derived from the earlier SPDY protocol, that's probably why the error message doesn't mention HTTP/2 at all.

:-( This site can’t be reached ERR_SPDY_PROTOCOL_ERROR

One of the reasons why you may see the ERR_SPDY_PROTOCOL_ERROR message is an invalid HTTP header coming from the server. Chrome is a bit more strict when processing binary HTTP/2, can't handle a header with a space instead of a dash (e.g. Referrer Policy instead of Referrer-Policy), or a header with double colon (e.g. Content-Security-Policy:: ...), so check your headers are being sent correctly. Firefox ignores such invalid HTTP header and will just display the page.

Here's how to find the offending header.

Chrome 71 and newer

Released in December 2018, Chrome 71 has removed chrome://net-internals/#events but luckily there's another way. A bit longer but just a few more clicks.

Go to chrome://net-export/ (copy and paste the link), click the Start Logging to Disk button, select where you want to store the network log file. Try loading your misbehaving site in another tab, then go back to chrome://net-export/, hit Stop Logging. The network log file contains events encoded as JSON so you'll need a viewer.

You can get a standalone web app or just use NetLog viewer available online. The online viewer processes the data locally in your browser, it does not upload your log files to Google or anyone else. So let's use it. Upload the JSON log file generated by the browser, and after a second or two you should see Data Loaded, creation date, Chrome version and some more info.

Select Events in the left menu, type your domain into the search box and click the row with HTTP2_SESSION in the Source Type column. HTTP2_SESSION Source Type in netlog-viewer.appspot.com

You can now skip to the Reading the log part.

Chrome 70 or older

Go to chrome://net-internals/#events (the link is not clickable, but you can just copy and paste it), type your domain into the search box there (I'll use example.com as, well, an example), then try loading your misbehaving site in a different tab. Go back to chrome://net-internals/#events and click the row with HTTP2_SESSION in the Source Type column.

chrome://net-internals/#events HTTP2_SESSION Source Type

The process described above for Chrome 71 works in older versions too. Using chrome://net-internals/#events was just a bit faster though.

Reading the log

Once you've loaded the events and selected the HTTP2_SESSION event type, you'll see an HTTP/2 protocol details on the right side, this is the important part:

t=155893 [st=3251]  HTTP2_SESSION_RECV_INVALID_HEADER
                    --> error = "Invalid character in header name."
                    --> header_name = "referrer%20policy"
                    --> header_value = "no-referrer,%20strict-origin-when-cross-origin"
t=155893 [st=3251]  HTTP2_SESSION_SEND_RST_STREAM
                    --> description = "Could not parse Spdy Control Frame Header."
                    --> error_code = "1 (PROTOCOL_ERROR)"
                    --> stream_id = 5

See the line with HTTP2_SESSION_RECV_INVALID_HEADER? There's an error description and an invalid header just below it, in this case it's the referrer policy header, with space (URL-encoded as %20) instead of dash. Header names in HTTP/2 must be converted to lowercase, so even if you send Referrer-Policy from your app, the browser sees referrer-policy. Or invalid referrer policy in this case.

You'll find a lot of interesting things about your browser internals in chrome://net-internals/ or in logs exported from chrome://net-export/ in newer versions. There are requests which are not even displayed in Developer Tools, like the ones coming from extensions. Go and check it out, might be handy next time you need to debug something. I've used it when researching Opera browser “VPN” or unencrypted “VPN” in UR browser.

Updates

December 6, 2018 Added chrome://net-export because Chrome 71 has removed chrome://net-internals/#events

Michal Špaček

I build web applications and I'm into web application security. I like to speak about secure development. My mission is to teach web developers how to build secure and fast web applications and why.

Public trainings

Come to my public trainings, everybody's welcome:

PHP application security
(December 11–12, 2018 Praha)

HTTPS for developers and admins
(December 13, 2018 Praha)