Short answer: SSL_ERROR_RX_RECORD_TOO_LONG means your browser sent a TLS handshake and got back something that is not TLS at all — almost always a plain HTTP server answering on the port you asked for HTTPS on. It is a Firefox error; Chrome and Edge show the same situation as ERR_SSL_PROTOCOL_ERROR. The fix on your own server is nginx or Apache listening for TLS on the port the client actually used.
Secure Connection Failed
An error occurred during a connection to example.com:443. SSL received a record that exceeded
the maximum permissible length.
Error code: SSL_ERROR_RX_RECORD_TOO_LONG“Record” is the TLS term for a chunk of the handshake. Firefox read a record whose declared length is absurd for TLS (over 16 KB, the protocol’s own cap), which happens when what arrived is plain text — an HTTP response line like HTTP/1.1 400 Bad Request — misread as if it were encrypted TLS framing. The browser never got a real certificate to fail on; it never got TLS at all.
The four situations that cause it
| Situation | What is really happening | Fix |
|---|---|---|
| https://example.com with no TLS configured | nginx/Apache answers port 443 in plain HTTP, or nothing listens on 443 and a load balancer forwards the raw TCP | Add a TLS server block for that port |
| A custom port, e.g. https://example.com:8443 | The app on 8443 is a plain HTTP server (common for admin panels, Node/Python dev servers) | Put a TLS-terminating reverse proxy in front, or use http:// if the panel is not meant to be public |
| Reverse proxy or load balancer in front | The proxy terminates TLS but forwards plain HTTP to the backend on the same port the client connected to, or health checks hit the backend port directly over HTTPS | Check which port the client reaches first; TLS has to terminate there, not deeper in the chain |
| Right after issuing a certificate or changing nginx config | The service was reloaded but the TLS <C>server</C> block for that port/host was never written, so the plaintext default_server answers | Confirm with the curl test below, then add or fix the block and reload |
Confirm it with curl and openssl
Two commands tell you for certain whether a port speaks TLS at all, without guessing from the browser error:
curl -v https://example.com:443/ 2>&1 | head -5
openssl s_client -connect example.com:443 -servername example.com -brief </dev/nullA working TLS port replies to the openssl command with CONNECTION ESTABLISHED and a protocol version. A port with this problem instead closes the connection, times out, or — the clearest sign — openssl prints unsupported or curl prints the raw plaintext HTTP response straight to your terminal:
* Trying 203.0.113.10:443...
* Connected to example.com (203.0.113.10) port 443
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to example.com:443
HTTP/1.1 400 Bad Request
Content-Type: text/plain
Connection: closeThat HTTP/1.1 400 Bad Request appearing where a TLS handshake should be is the plaintext record the browser calls “too long”: nginx is running on 443, but in HTTP mode, and it is rejecting the raw TLS bytes as a malformed HTTP request. The ERR_SSL_PROTOCOL_ERROR article goes through the same signature for Chrome’s wording of this error.
Fix it in nginx
The usual cause is a server block that listens on 443 without ssl, or two blocks where the plain one wins because it is first, has no server_name match, or is marked default_server:
server {
listen 443;
server_name example.com;
root /var/www/example;
}server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example;
}sudo nginx -t && sudo systemctl reload nginxIf Certbot issued the certificate already, sudo certbot --nginx -d example.com writes this block for you and is the safer path if you are not sure the paths above are exactly right. The full set of nginx TLS directives, including the redirect from port 80 and OCSP stapling, is in nginx reverse proxy with SSL.
Fix it in Apache
The same mistake in Apache is a VirtualHost *:443 block with no SSLEngine on, or the ssl module never enabled:
sudo a2enmod ssl
sudo a2ensite default-ssl # or your own TLS vhost file<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
DocumentRoot /var/www/example
</VirtualHost>sudo apachectl configtest && sudo systemctl reload apache2A custom port, not 443
Admin panels and dashboards (phpMyAdmin behind a dev server, Grafana, a Node app in development) often bind an HTTP-only listener on a high port and get reached with https://host:8443 out of habit or because a bookmark was saved wrong. Two honest fixes:
- If the service should stay internal, use
http://on that port over a VPN or SSH tunnel, and do not expose it publicly at all. - If it needs to be public, put nginx or Apache in front as a reverse proxy that terminates TLS on the public port and forwards plain HTTP to the app on localhost, rather than trying to make the app itself speak TLS.
Behind Cloudflare or another proxy
When a CDN or load balancer sits in front, the browser’s TLS handshake ends at that edge, not at your server — so this error there means the edge itself is misconfigured for the hostname (a wrong origin port in its settings, usually), not your server. If the edge is fine but direct health checks against your origin’s IP over HTTPS show this error, the origin is answering the health check in plain HTTP on the port the check uses; point the check at the right port or add TLS there too. The SSL checker connects to a host and port directly and shows the handshake (or its absence) the same way the commands above do, without needing a terminal.
What this is not
This is not an expired, self-signed or mismatched certificate — those all complete a TLS handshake and then fail on the certificate itself, which Firefox shows as SEC_ERROR_EXPIRED_CERTIFICATE or similar, and which your connection is not private covers. SSL_ERROR_RX_RECORD_TOO_LONG happens before any certificate is exchanged at all, which is exactly why openssl s_client and curl above are the right first check rather than looking at the certificate.
Frequently asked
What does SSL_ERROR_RX_RECORD_TOO_LONG mean?
Firefox tried a TLS handshake and received a record whose declared length makes no sense for TLS — almost always because what actually arrived was a plain-text HTTP response, not an encrypted TLS record at all. The server is answering that port in plain HTTP.
Is this the same as ERR_SSL_PROTOCOL_ERROR in Chrome?
Yes, for this specific cause. Firefox and Chrome use different error names for the same underlying situation — a non-TLS reply where TLS was expected — so the fix is identical regardless of which browser reported it.
How do I confirm the real cause before changing anything?
Run openssl s_client -connect host:443 -servername host -brief </dev/null. A working TLS port answers CONNECTION ESTABLISHED with a protocol version; a port with this problem fails the handshake or returns plain HTTP text, such as an HTTP/1.1 400 Bad Request line, straight into the terminal.
I have a valid certificate — why does this still happen?
The certificate is irrelevant here: this error happens before any certificate is exchanged, because the port is not running TLS at all on the connection the client made. Check that an ssl server block (nginx) or SSLEngine on (Apache) actually exists and is loaded for that exact host and port.