Skip to content
ecdsa.com
curl (OpenSSL backend)curl

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

How to fix it

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

Browse the full signature error database