What the error means
Chrome shows “This site can’t provide a secure connection. example.com sent an invalid response.” with ERR_SSL_PROTOCOL_ERROR underneath.
An HTTPS connection starts with a TLS handshake. Chrome sends a ClientHello and expects a ServerHello back, followed by a certificate and key exchange, all in a precisely defined format. ERR_SSL_PROTOCOL_ERROR means what came back wasn’t that: the bytes didn’t parse as TLS, or a message arrived out of order, or the connection broke down in a way Chrome classifies as a protocol violation.
That narrows things down usefully. The connection itself worked: DNS resolved and the TCP connection to port 443 opened. Otherwise you’d see ERR_NAME_NOT_RESOLVED or ERR_CONNECTION_REFUSED. And the handshake never got as far as checking the certificate. Otherwise you’d see one of the NET::ERR_CERT_* errors.
| What Chrome says | Where it stopped |
|---|---|
ERR_CONNECTION_REFUSED / ERR_CONNECTION_TIMED_OUT | No TCP connection to port 443 |
ERR_SSL_PROTOCOL_ERROR | Connected, but the reply wasn’t valid TLS or the handshake broke the rules |
ERR_SSL_VERSION_OR_CIPHER_MISMATCH | Both spoke TLS, but agreed on no version or cipher (or a CDN has no certificate for the name) |
NET::ERR_CERT_* | Handshake reached the certificate, and the certificate failed a check |
Firefox reports the most common variant, plain HTTP on an HTTPS port, as SSL_ERROR_RX_RECORD_TOO_LONG.
Is it you or the site?
Settle this first; it decides everything else.
- Open the URL on your phone over mobile data, with Wi-Fi off.
- Run the SSL check. It performs a real handshake from a server in Seoul and shows the negotiated TLS version and certificate, or says the handshake failed.
If it fails everywhere, the site is broken; skip to the site owner section. If it works elsewhere and fails only on your device or network, read the next section.
If you’re a visitor
The usual suspects, roughly in order of how often they’re involved:
- Antivirus or firewall with HTTPS scanning. It sits in the middle of every TLS connection and occasionally mangles one. Turn off the HTTPS/SSL scanning option temporarily and retry.
- VPN or proxy. Disconnect and retry. Check whether a system proxy is set (on Windows: Settings → Network & Internet → Proxy).
- A filtering or captive network. Some hotel, school and office networks answer port 443 themselves, with a login or block page in plain HTTP. To the browser that’s exactly a protocol error. Open an
http://page to trigger the login screen, or try another network. - A wrong system clock. It normally causes certificate date errors rather than this one, but it takes a second to check.
Much of the advice online (clear the SSL state, flush DNS, disable QUIC, reset Chrome) is aimed at problems you almost certainly don’t have. If the site fails for everyone, none of it helps. If it fails only for you, the list above is where the cause usually is.
If you run the site: look at what port 443 actually says
Two commands tell you nearly everything:
curl -v https://www.example.com/ -o /dev/null
openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null
What the output tells you:
wrong version number (or packet length too long):
curl: (35) OpenSSL/3.0.13: error:0A00010B:SSL routines::wrong version number
The server answered, but not with TLS. Something is speaking plain HTTP on port 443. Confirm it:
curl -v http://www.example.com:443/
If that returns a normal HTTP response, you’ve found it (cause 1 below). If instead nginx answers 400 The plain HTTP request was sent to HTTPS port, port 443 is speaking TLS correctly and the problem is elsewhere.
unexpected eof while reading or no peer certificate available with nothing else: the server accepted the connection and hung up without completing the handshake. Typical of a TLS listener with no usable certificate for this name (cause 3), or a middlebox resetting the connection (cause 5).
alert handshake failure or alert protocol version: the server is speaking TLS and rejecting your offer. That’s really a version or cipher problem; see ERR_SSL_VERSION_OR_CIPHER_MISMATCH.
Connection refused or a timeout: no TLS problem yet; nothing is reachable on 443. Use the port check and look at firewalls and listeners, as in ERR_CONNECTION_REFUSED.
The usual causes on the server
1. Plain HTTP on port 443
By a wide margin the most common cause. Some ways to end up here:
-
nginx with
listen 443;instead oflisten 443 ssl;. nginx serves plain HTTP on 443 without complaint. Find every 443 listener across all included files:sudo nginx -T 2>/dev/null | grep -nE 'listen\s+[^;]*443'Every line should contain
ssl. -
Apache with a
<VirtualHost *:443>that lacksSSLEngine on, or withmod_sslnot loaded (apachectl -M | grep ssl). -
Docker publishing the wrong container port:
-p 443:80sends HTTPS traffic to a container port that speaks HTTP.docker psshows the mapping as0.0.0.0:443->80/tcp. -
A TCP load balancer (for example an AWS Network Load Balancer with a TCP listener on 443) forwarding to the backend’s HTTP port. A TCP listener passes bytes through untouched; either terminate TLS on the load balancer (a TLS listener with a certificate) or forward to a backend port that speaks TLS.
-
Home and office routers forwarding external 443 to an internal port 80.
-
An application server (Node.js, Python, Go) started with
http.createServeror plainlistenon 443, behind no TLS proxy.
Fix: make whatever listens on 443 speak TLS, with a certificate. With nginx:
server {
listen 443 ssl;
http2 on; # nginx 1.25.1+; on older versions use "listen 443 ssl http2;"
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
}
Then sudo nginx -t && sudo systemctl reload nginx.
2. HTTPS sent to a port that only speaks HTTP
The mirror image: the server is right, the URL isn’t. https://example.com:8080/ against an app server that only does HTTP on 8080 gives the same error. Look for links, redirects or configuration that add https:// to a development or admin port.
One local-development trap: the .dev and .app top-level domains are on Chrome’s HSTS preload list, so Chrome always uses HTTPS for them. If your local setup uses myproject.dev and serves plain HTTP, Chrome upgrades the request and gets HTTP back. Use localhost, a .test name, or set up local TLS with mkcert.
3. TLS is configured, but not for this hostname
The server speaks TLS on 443 and picks a configuration based on the SNI name. If no server block matches, nginx falls back to the default for that port; if the default has no certificate, the handshake can’t complete. nginx’s error log shows:
no "ssl_certificate" is defined in server listening on SSL port while SSL handshaking
Depending on exactly how the server gives up, Chrome reports this as ERR_SSL_PROTOCOL_ERROR or ERR_CONNECTION_CLOSED. The test: openssl s_client -servername works for one name and fails for another against the same IP.
Check the logs right after reproducing it:
sudo tail -n 50 /var/log/nginx/error.log
sudo journalctl -u nginx --since "10 min ago"
Fix: add the hostname to server_name in a block that has ssl_certificate, and give the default server a certificate (or ssl_reject_handshake on; on nginx 1.19.4+, which rejects unknown names cleanly instead of leaving the handshake half-done).
On managed hosting, the same thing happens briefly right after adding a domain, before the host has issued a certificate for it. Check the hosting panel’s SSL status before debugging anything else.
4. The TLS layer is broken or misconfigured
Less common, but they turn up:
- A certificate or key file the server can read but not use: empty, truncated, in the wrong format (DER where PEM is expected), or a key that doesn’t match the certificate. Most servers refuse to start in that case, but some application servers only fail when a connection arrives. Check that the files load:
openssl x509 -in fullchain.pem -noout -subjectandopenssl pkey -in privkey.pem -noout. - A TLS-terminating proxy in front of another one, where one hop expects TLS and the other sends HTTP. Draw the path from the visitor to the application and mark at each hop whether it’s HTTP or HTTPS. The mismatch is usually visible immediately.
- Old or embedded TLS stacks that answer modern ClientHellos incorrectly. Updating the software or the TLS library is the fix; there’s no setting on the client side that a visitor could reasonably change.
5. A middlebox in front of the server
An IPS, web application firewall, DDoS filter or “SSL inspection” appliance between the internet and your server can reset or alter handshakes it doesn’t like. The symptom is that the handshake works from inside the network (or on the server against 127.0.0.1) and fails from outside.
Compare the two:
# on the server itself
openssl s_client -connect 127.0.0.1:443 -servername www.example.com </dev/null
and the SSL check from outside. If the inside works and the outside doesn’t, look at the devices in between, and at their logs at the time of a failed attempt.
Not this error: ERR_SSL_VERSION_OR_CIPHER_MISMATCH
The two are often confused. With ERR_SSL_VERSION_OR_CIPHER_MISMATCH the server speaks correct TLS and answers with a proper alert: it has no protocol version or cipher suite in common with the browser, or, very often on CDNs, no certificate at all for the requested name. An old server that only offers TLS 1.0 or 1.1 usually shows up this way, not as a protocol error. The fixes are different: enabling TLS 1.2 and 1.3 and modern ciphers, or getting the CDN to issue a certificate for the name. See ERR_SSL_VERSION_OR_CIPHER_MISMATCH.
If you’re behind Cloudflare, visitors talk TLS to Cloudflare, not to you, so a broken origin produces Cloudflare’s error 525 rather than ERR_SSL_PROTOCOL_ERROR.
After the fix
curl -v https://www.example.com/completes the handshake and prints the HTTP status.curl -v http://www.example.com:443/no longer returns a normal page.- The SSL check shows the certificate and a negotiated TLS version (1.2 or 1.3).
- The HTTP check shows that
http://redirects tohttps://once, without a loop. - Test every hostname that points at the server, not just the one that was reported.
Related errors
- ERR_SSL_VERSION_OR_CIPHER_MISMATCH: valid TLS, but no common version or cipher, or no certificate for the name on a CDN.
- ERR_CONNECTION_REFUSED: nothing accepts the connection on the port.
- Cloudflare error 525: the same kind of handshake failure between Cloudflare and your origin.
- NET::ERR_CERT_AUTHORITY_INVALID: the handshake worked, but the certificate isn’t trusted.
Check your own domain now
Free, no sign-up. Runs the exact check this guide describes and shows what to fix.