Knowledge

SSL handshake failed in nginx (ERR_SSL_PROTOCOL_ERROR)

#Errors

A failed TLS handshake means the browser and nginx could not agree on a secure connection. The top causes are an expired certificate and a missing intermediate chain.

Published by Mark van Eijk on June 23, 2026 · 1 minute read

  1. About the error
  2. Why do I see this error
  3. Diagnose
  4. Solution
  5. Renew an expired certificate
  6. Serve the full chain
  7. Enforce modern protocols

About the error

The visitor sees ERR_SSL_PROTOCOL_ERROR or "SSL handshake failed", and nginx logs an SSL error. The handshake is the negotiation that happens before any data is exchanged, if it fails, the page never loads over HTTPS.

Why do I see this error

In production, almost always one of these:

  • Expired certificate, the single most common cause.
  • Missing intermediate certificate, nginx doesn't auto-fetch the chain, you must bundle it.
  • Wrong certificate or key path in the config, or a key that doesn't match the certificate.
  • Outdated protocol, the client requires TLS 1.2+ and the server only offers older versions.

Diagnose

Test the live handshake with OpenSSL. The -servername flag sends SNI, which is required when one IP serves multiple certificates:

openssl s_client -connect example.com:443 -servername example.com

Check the certificate's expiry date directly:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates

And read the nginx error log:

tail -f /var/log/nginx/error.log

Solution

Renew an expired certificate

With Certbot:

sudo certbot renew
sudo systemctl reload nginx

Automate renewal so it never expires again, Certbot installs a timer for this by default.

Serve the full chain

The ssl_certificate directive must point at the full chain (your certificate followed by the intermediates), not just the leaf certificate:

ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

A leaf-only certificate works in some browsers and fails in others, a classic intermittent handshake failure.

Enforce modern protocols

ssl_protocols TLSv1.2 TLSv1.3;

Validate and reload after any change:

nginx -t && systemctl reload nginx

For a hardened SSL setup behind a CDN, see an A+ grade SSL using Cloudflare. If the chain problem shows up in command-line tools rather than browsers, see curl (60) SSL certificate problem. When these same problems reach a visitor's browser, they see your connection is not private or NET::ERR_CERT_AUTHORITY_INVALID.

Subscribe to our newsletter

Do you want to receive regular updates with fresh and exclusive content to learn more about web development, hosting, security and performance? Subscribe now!

Related articles

Error in the HTTP2 framing layer

A failed TLS handshake means the browser and nginx could not agree on a secure connection. The top causes are an expired certificate and a missing intermediate chain.

Read more →

413 Request Entity Too Large in nginx

A failed TLS handshake means the browser and nginx could not agree on a secure connection. The top causes are an expired certificate and a missing intermediate chain.

Read more →