Error message
curl: (35) TLS connect error: error:0A000410:SSL routines::ssl/tls alert handshake failure
Also printed as
- curl: (35) error:0A000410:SSL routines::sslv3 alert handshake failure
- curl: (35) error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure
OpenSSL 3.3 and later spell the reason "ssl/tls alert handshake failure"; OpenSSL 3.1 and older print "sslv3 alert handshake failure" (1.1.1 with the code 14094410 and the function name ssl3_read_bytes). Recent curl releases put "TLS connect error: " in front.
curl (OpenSSL backend) — CURLE_SSL_CONNECT_ERROR, TLS alert 40 (handshake_failure) received from the server
What it means
The server ended the handshake by sending TLS alert 40 (handshake_failure): it could not agree on parameters with what curl offered. The alert carries no reason, so the actual cause is only visible in the server's logs; on the client side you can only compare what each side supports. The certificate was not checked yet at this point.
Why it happens
No cipher suite in common
commonLegacy servers restricted to RSA key exchange, CBC-SHA1, 3DES or RC4 suites meet clients that removed them; hardened servers allowing only a few suites meet old clients that lack them. Either way the server finds nothing acceptable in the ClientHello.
The client did not send the server name (SNI)
commonCDNs and multi-tenant proxies (Cloudflare, AWS CloudFront, many Kubernetes ingresses) reject handshakes without SNI or for names they do not serve. Connecting by IP address or with tools that omit SNI triggers exactly this alert.
The server requires a client certificate
occasionalSome TLS 1.2 servers configured for mutual TLS answer a missing or unacceptable client certificate with handshake_failure instead of the more specific certificate alerts.
No common key-exchange group or signature algorithm
occasionalA server that only allows a post-quantum hybrid group, a brainpool curve or RSA-PSS signatures can fail against clients that do not offer them, and vice versa for very old clients.
How to fix it
- 1.
Compare what both sides offer
Enumerate the server's suites and protocol versions and compare them with the client's cipher list. curl's verbose output shows how far the handshake got and which version was attempted.
bash # What the server accepts (needs nmap) and what this client offers nmap --script ssl-enum-ciphers -p 443 api.example.com openssl ciphers -v 'DEFAULT' | head -20 # Full handshake trace from curl's side curl -sv https://api.example.com/ -o /dev/null 2>&1 | grep -E '^\* (TLS|SSL)' - 2.
Server owners: use a current cipher configuration
Offer TLS 1.3 and TLS 1.2 with ECDHE and AEAD suites (AES-GCM, ChaCha20-Poly1305). Every client from the last decade supports them, and nothing modern needs RSA key exchange or CBC.
text # nginx: TLS 1.2 with forward-secret AEAD suites + TLS 1.3 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; ssl_prefer_server_ciphers off; - 3.
Send the right server name
Use the host name in the URL, or keep the name while forcing an address with --resolve. With openssl s_client, pass -servername when connecting to an IP.
bash # Use the host name in the URL (SNI is sent automatically); for IP targets keep the name: curl --resolve api.example.com:443:203.0.113.10 https://api.example.com/ # openssl s_client sends SNI only with -servername (or a host name in -connect) openssl s_client -connect 203.0.113.10:443 -servername api.example.com </dev/null - 4.
Present the client certificate for mutual TLS
If the endpoint is an mTLS API, configure the client certificate and key; the server's logs will name the problem as a missing or unverifiable peer certificate.
bash # Mutual TLS: present the client certificate and key the server expects curl --cert client.pem --key client.key https://mtls.example.com/
Related errors
SSL routines:ssl3_read_bytes:ssl/tls alert handshake failureOpenSSL (client side)SSL routines:tls_post_process_client_hello:no shared cipherOpenSSL (server side: nginx, Apache, Postfix, openssl s_server)curl: (35) TLS connect error: error:0A00042E:SSL routines::tlsv1 alert protocol…curl (OpenSSL backend)[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] ssl/tls alert handshake failurePython ssl module (OpenSSL)javax.net.ssl.SSLHandshakeException: (handshake_failure) Received fatal alert:…Java (JSSE)