Guides
9 min readUpdatedERR_SSL_PROTOCOL_ERROR: why the secure connection fails and how to fix it
Quick answer
ERR_SSL_PROTOCOL_ERROR means Chrome started a TLS handshake and received something it could not accept, so it stopped before loading any page. Chrome uses the code as a catch-all for handshake failures that have no more specific error. The usual causes are a server or proxy answering in plain HTTP on port 443, a faulty TLS terminator, or security software or a middlebox interfering. One openssl s_client test shows which.
01What does ERR_SSL_PROTOCOL_ERROR mean?
Chrome shows the error under the heading "This site can't provide a secure connection", with the line "example.com sent an invalid response". The TLS handshake, which sets up encryption before any HTTP request is sent, failed in a way Chrome could not classify more precisely. No page, cookie or cached file is involved yet.
The code is a catch-all by design. In Chromium's source, the function that translates TLS library errors into browser errors returns ERR_SSL_PROTOCOL_ERROR for anything without a more specific case. No shared version or cipher gives ERR_SSL_VERSION_OR_CIPHER_MISMATCH, a rejected client certificate ERR_BAD_SSL_CLIENT_AUTH_CERT, and an unrecognised name ERR_SSL_UNRECOGNIZED_NAME_ALERT. What remains is mostly malformed or unexpected data, such as plain HTTP where TLS was expected.
02Who can fix it? Causes sorted by fixer
The browser always generates the error, but the cause sits with the site owner, the visitor, or a device in between. Knowing which saves hours of fixes on the wrong side.
| Cause | Who fixes it | How to recognise it | Test |
|---|---|---|---|
| Port 443 answers in plain HTTP: a listener without TLS, or a port forward to port 80 | Site owner | Fails for everyone; curl says wrong version number, Firefox usually SSL_ERROR_RX_RECORD_TOO_LONG | openssl s_client; curl -sI http://example.com:443/ |
| A TLS terminator or load balancer that drops or corrupts handshakes | Site owner or host | Fails from every network, often intermittently | Repeat s_client; read the terminator's logs |
| Antivirus HTTPS scanning, a corporate proxy or a VPN | Visitor or their IT team | Works on another network, or with the scanner paused | Compare networks and devices |
| A middlebox that mishandles a large ClientHello | Whoever runs the middlebox | Only current Chrome fails, often with resets | Compare with curl or an older client |
| Only TLS 1.0 or 1.1, or obsolete ciphers | Site owner | Chrome shows ERR_SSL_VERSION_OR_CIPHER_MISMATCH instead | s_client with -tls1_2 and -tls1_3 |
| No certificate or site configured for the name, at the server or CDN edge | Site owner | Starts after a DNS or proxy change; often a version-or-cipher or unrecognised-name error instead | s_client with the exact -servername |
03How to fix ERR_SSL_PROTOCOL_ERROR as a visitor
First find out whether the error follows you or the site: open the address on another device using mobile data. If it fails there too, only the site owner can fix it; send them the time and the exact error. If it works there, try these steps in order.
- Pause the HTTPS or web scanning in your antivirus or internet security suite, then reload.
- Turn off the VPN, proxy or filtering DNS service, then reload.
- Open the page in a private window to rule out extensions.
- Update Chrome, and the operating system on older devices.
- Check the date and time; a wrong clock usually causes a certificate error instead, but it takes seconds to rule out.
- On a company or school network, ask IT whether a proxy inspects HTTPS.
04How to test the TLS handshake with openssl s_client
openssl s_client performs the handshake on its own and prints what went wrong. Always pass -servername, which sends the hostname as SNI the way a browser does, and -verify_hostname, which checks the certificate against that name. Without SNI, a server hosting several sites may present the wrong certificate or reject the handshake.
With -brief, a working handshake prints CONNECTION ESTABLISHED, the protocol version, the cipher suite and Verification: OK. Verification failures stay warnings unless you add -verify_return_error. A server that refuses TLS 1.3 but accepts TLS 1.2 still works in current browsers.
The last command sends plain HTTP to the TLS port on purpose. Correctly configured servers answer 400 or close the connection (nginx adds "The plain HTTP request was sent to HTTPS port"). A 200 means port 443 serves plain HTTP; so does a redirect, unless the server deliberately redirects plain requests sent to its TLS port, which nginx can do with error_page 497.
# full handshake with SNI and a hostname check
openssl s_client -connect example.com:443 -servername example.com -verify_hostname example.com -brief </dev/null
# which TLS versions does the server accept?
openssl s_client -connect example.com:443 -servername example.com -tls1_3 -brief </dev/null
openssl s_client -connect example.com:443 -servername example.com -tls1_2 -brief </dev/null
# make certificate problems fatal instead of a warning
openssl s_client -connect example.com:443 -servername example.com -verify_hostname example.com -verify_return_error -brief </dev/null
# does port 443 answer in plain HTTP?
curl -sI http://example.com:443/05What the openssl output means
These are real lines from the tests above, run against badssl.com's deliberately broken hosts and local test servers with OpenSSL 3.0.22 on Debian 12; other builds word them slightly differently.
| openssl prints | Meaning | What Chrome shows | Fix |
|---|---|---|---|
| CONNECTION ESTABLISHED ... Verification: OK | The handshake works from here | No TLS error; suspect the visitor's network | Compare networks |
| ssl3_get_record:wrong version number | Plain HTTP where TLS was expected | ERR_SSL_PROTOCOL_ERROR | Enable TLS on that listener or fix the port mapping |
| tlsv1 unrecognized name ... SSL alert number 112 | No site configured for that name | ERR_SSL_UNRECOGNIZED_NAME_ALERT | Add the hostname to the server or proxy |
| sslv3 alert handshake failure ... SSL alert number 40 (with -tls1_3) | That version is not accepted | Nothing, if TLS 1.2 works | Enable TLS 1.3 when convenient |
| legacy sigalg disallowed or unsupported (TLS 1.0-only host) | Stuck on TLS 1.0 or 1.1 | ERR_SSL_VERSION_OR_CIPHER_MISMATCH | Enable TLS 1.2 and 1.3 |
| verify error:num=62:hostname mismatch, or num=10:certificate has expired | A certificate problem | A NET::ERR_CERT privacy warning | Fix or renew the certificate |
06Why plain HTTP on port 443 breaks the handshake
The most common cause is easy to show with arithmetic. A TLS client expects the server's first five bytes to be a record header: one byte of content type, two of version, two of length. A misconfigured server answering in plain HTTP starts its reply with HTTP/1.1 400, and the client reads those characters as the header.
The fix is to enable TLS on the listener behind port 443, or to point the port forward or load balancer at the TLS port instead of port 80.
- Byte 1, H (0x48), is read as the content type; RFC 8446 defines content types 20 to 23 (0x14 to 0x17).
- Bytes 2 and 3, TT (0x5454), are read as the version; a real record's version starts with 0x03, so OpenSSL reports wrong version number.
- Bytes 4 and 5, P/ (0x502F), are read as the length: 20,527 bytes, above the 16,384-byte (2^14) maximum in RFC 8446 section 5.1, which is why Firefox typically reports SSL_ERROR_RX_RECORD_TOO_LONG.
- Chrome has no specific code for either failure, so it shows ERR_SSL_PROTOCOL_ERROR.
server {
listen 443 ssl; # without "ssl" nginx answers plain HTTP here
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
}07SSL handshake failed between a CDN and the origin
Behind a CDN or reverse proxy there are two TLS connections: browser to edge, and edge to origin. Only a failure on the first produces ERR_SSL_PROTOCOL_ERROR. A failure on the second is seen by the edge, which answers with an error page of its own: a 502 Bad Gateway from nginx, or a 525 "SSL handshake failed" or 526 page from Cloudflare, both listed in the Cloudflare error codes reference.
Providers call the edge-to-origin setting an SSL or encryption mode. In Cloudflare's terms, Flexible connects to the origin over HTTP, Full over HTTPS without certificate validation, and Full (strict) over HTTPS with it. Each failing row was reproduced with nginx as the proxy.
| Edge connects to the origin with | The origin serves | Result | Who shows what |
|---|---|---|---|
| HTTP to port 80 | HTTP that redirects to HTTPS | Endless redirects | Browser: ERR_TOO_MANY_REDIRECTS |
| HTTPS to port 443 | Plain HTTP on 443 | Handshake fails: wrong version number | Edge: 502, or 525 at Cloudflare |
| HTTP to port 443 | TLS on 443 | Origin answers 400 | Visitor sees that 400 |
| HTTPS without SNI | Several sites, chosen by name | unrecognized name alert, or the wrong certificate | Edge: 502 or 525 |
| HTTPS with certificate checks | A self-signed, expired or incomplete chain | certificate verify error | Edge: 502, or 526 at Cloudflare |
| HTTPS with SNI and checks | A valid certificate for the name | Works, encrypted end to end | Nothing |
# error.log lines from the failing combinations
SSL_do_handshake() failed (SSL: error:0A00010B:SSL routines::wrong version number) while SSL handshaking to upstream
SSL_do_handshake() failed (SSL: error:0A000458:SSL routines::tlsv1 unrecognized name:SSL alert number 112) while SSL handshaking to upstream
upstream SSL certificate verify error: (21:unable to verify the first certificate) while SSL handshaking to upstream
# proxy to an HTTPS origin with SNI and certificate checks
location / {
proxy_pass https://203.0.113.10;
proxy_set_header Host $host;
proxy_ssl_server_name on; # send SNI; nginx's default is off
proxy_ssl_name example.com; # the name on the origin's certificate
proxy_ssl_verify on; # also off by default
proxy_ssl_verify_depth 2;
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
}08How site owners fix ERR_SSL_PROTOCOL_ERROR
Work down the list from the top; the first items cover most cases.
- Make sure every listener on port 443 has TLS enabled, and that port forwards, NAT rules and load balancers send port 443 to the TLS port, not to port 80.
- Offer TLS 1.2 and TLS 1.3. RFC 8996 deprecated TLS 1.0 and 1.1 in March 2021, and since Chrome 98 the error for sites without TLS 1.2 cannot be bypassed.
- Serve a certificate for every hostname, including www, and after moving behind a CDN confirm the edge certificate covers them all before switching DNS.
- If failures are intermittent, read the TLS terminator's or load balancer's logs; a worker dying mid-handshake can look like random protocol errors.
- If only recent Chrome versions fail, test servers and middleboxes with a large ClientHello and update them; tldr.fail lists affected products.
- Under a TLS handshake flood, a terminator out of capacity drops handshakes, which visitors see as resets and timeouts.
09What a reverse proxy can and cannot fix
A TLS-terminating proxy takes over the browser-facing handshake. Visitors get the proxy's TLS versions, ciphers and certificates whatever the origin runs, which removes most owner-side causes of this error, and the proxy can encrypt and verify its own connection to the origin.
It cannot fix the visitor's side: antivirus that intercepts HTTPS, a corporate proxy or a broken middlebox still breaks the handshake before the proxy sees it. It cannot help while DNS points at the old server, and an origin TLS mismatch becomes an edge status page rather than going away. Configuring the origin leg, with SNI and a valid certificate, stays the owner's job.
Questions
Frequently asked questions
Sources
Sources and verification
openssl s_client and curl outputs captured with OpenSSL 3.0.22 and curl 7.88.1 on Debian 12, October 2026, against example.com, badssl.com test hosts and local nginx 1.22.1 test servers (plain HTTP on a TLS port, SNI rejection, and the three edge-to-origin failures). The listener snippet was checked with nginx -t, the proxy snippet with nginx -t and live requests.
- 01net_error_list.h: ERR_SSL_PROTOCOL_ERROR and related codes · Chromium
- 02openssl_ssl_util.cc: mapping TLS library errors to browser errors · Chromium
- 03error_page_strings.grdp: error page headings and messages · Chromium
- 04RFC 8446: TLS 1.3, section 5.1 Record Layer · RFC Editor
- 05RFC 8996: Deprecating TLS 1.0 and TLS 1.1 · RFC Editor
- 06TLS 1.0 and TLS 1.1 removal · Chrome Platform Status
- 07openssl-s_client manual (OpenSSL 3.0) · OpenSSL
- 08SSLerrs.h: NSS error strings used by Firefox · Mozilla
- 09Module ngx_http_proxy_module: proxy_ssl_server_name, proxy_ssl_verify · nginx.org
- 10Module ngx_http_ssl_module · nginx.org
- 11SSL/TLS encryption modes · Cloudflare docs
- 12ERR_SSL_VERSION_OR_CIPHER_MISMATCH · Cloudflare docs
- 13Error 525: SSL handshake failed · Cloudflare docs
- 14Error 526: invalid SSL certificate · Cloudflare docs
- 15badssl.com: test hosts for broken TLS configurations · badssl.com
- 16tldr.fail: servers that fail on a large TLS ClientHello · tldr.fail
Last updated