A handshake failure means client and server never agreed on protocol version, cipher, or certificate. Narrow it down with openssl in one command and fix the mismatch.
Old clients fail against modern TLS-only servers (TLS 1.0/1.1 disabled), and new clients fail against ancient servers. openssl s_client -connect host:443 -tls1_2 prints the exact failure point.
A server serving the default (wrong) cert for a name will fail strict clients. openssl s_client -connect host:443 -servername host shows which cert is actually presented.
Connecting TLS to a plain HTTP port produces immediate handshake garbage. Test: curl -v https://host:8443/ and confirm the service really speaks TLS there.
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>&1 | head -25
openssl s_client -connect example.com:443 -servername example.com </dev/null | openssl x509 -noout -subject -dates
# ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; then: sudo nginx -t && sudo systemctl reload nginx
curl -vI https://example.com 2>&1 | grep -E 'subject|issuer|SSL connection'
TLS 1.3 changes the handshake design — failures there are almost always cert or middlebox problems, not cipher negotiation. openssl s_client -tls1_2 vs -tls1_3 quickly isolates which protocol path breaks.
Different clients support different TLS versions and cipher lists. Capture the exact negotiation with openssl s_client from a failing client if possible, then widen the server's ssl_protocols/ssl_ciphers to overlap.
Yes — deep-packet-inspection middleboxes that mangle ClientHello can abort handshakes. The tell is openssl succeeding from the server itself but failing across the network path.
Our most-documented failures, packaged as ready-to-ship starter kits: Docker, Kubernetes, and Terraform.
Browse the template store →One-time. Yours to modify. Instant download from the NinjaOps template store.