Troubleshooting

SSL_ERROR_RX_RECORD_TOO_LONG: plain HTTP answered where TLS was expected

Firefox's SSL_ERROR_RX_RECORD_TOO_LONG means the server answered in plain HTTP on a port a browser expected TLS on. How to confirm it with curl and openssl and fix it in nginx or Apache.

Published ~6 min read

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.

Example error (Firefox)
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

SituationWhat is really happeningFix
https://example.com with no TLS configurednginx/Apache answers port 443 in plain HTTP, or nothing listens on 443 and a load balancer forwards the raw TCPAdd a TLS server block for that port
A custom port, e.g. https://example.com:8443The 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 frontThe 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 HTTPSCheck which port the client reaches first; TLS has to terminate there, not deeper in the chain
Right after issuing a certificate or changing nginx configThe service was reloaded but the TLS <C>server</C> block for that port/host was never written, so the plaintext default_server answersConfirm 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:

Debian · Ubuntu · macOS
curl -v https://example.com:443/ 2>&1 | head -5
openssl s_client -connect example.com:443 -servername example.com -brief </dev/null

A 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:

Example output of the curl command on a misconfigured port
*   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: close

That 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:

before: plain HTTP on 443 (the bug)
server {
    listen 443;
    server_name example.com;
    root /var/www/example;
}
after: TLS terminates on 443
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;
}
apply it
sudo nginx -t && sudo systemctl reload nginx

If 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:

Debian · Ubuntu
sudo a2enmod ssl
sudo a2ensite default-ssl    # or your own TLS vhost file
/etc/apache2/sites-available/example-le-ssl.conf
<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>
apply it
sudo apachectl configtest && sudo systemctl reload apache2

A 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.

Related articles

All troubleshooting