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
- About the error
- Why do I see this error
- Diagnose
- Solution
- Renew an expired certificate
- Serve the full chain
- 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.
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.