错误信息
curl: (35) TLS connect error: error:0A000410:SSL routines::ssl/tls alert handshake failure
也可能输出为
- 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 及更高版本把原因写作“ssl/tls alert handshake failure”;OpenSSL 3.1 及更早版本打印的是“sslv3 alert handshake failure”(1.1.1 还会带上代码 14094410 和函数名 ssl3_read_bytes)。较新的 curl 版本会在前面加上“TLS connect error: ”。
curl (OpenSSL backend) — CURLE_SSL_CONNECT_ERROR, TLS alert 40 (handshake_failure) received from the server
含义
服务器发送了 TLS 告警 40(handshake_failure),结束了握手:它无法与 curl 提供的参数达成一致。这个告警不携带原因,所以真正的原因只能在服务器日志中看到;在客户端只能比较双方各自支持的内容。此时还没有开始检查证书。
原因
没有共同的密码套件
常见只支持 RSA 密钥交换、CBC-SHA1、3DES 或 RC4 套件的旧服务器,遇上已经移除这些套件的客户端;只允许少数几个套件的加固服务器,遇上不支持这些套件的旧客户端。无论哪种情况,服务器都无法在 ClientHello 中找到可接受的选项。
客户端没有发送服务器名称(SNI)
常见CDN 和多租户代理(Cloudflare、AWS CloudFront、许多 Kubernetes Ingress)会拒绝不带 SNI 的握手,也会拒绝请求它们不提供服务的名称的握手。通过 IP 地址连接,或者使用不发送 SNI 的工具,触发的正是这个告警。
服务器要求客户端证书
偶见部分配置了双向 TLS 的 TLS 1.2 服务器,在客户端证书缺失或不被接受时,会用 handshake_failure 回应,而不是发送更具体的证书相关告警。
没有共同的密钥交换组或签名算法
偶见只允许后量子混合组、brainpool 曲线或 RSA-PSS 签名的服务器,在客户端不提供这些选项时可能握手失败;非常旧的客户端则反过来也会出现这种情况。
修复方法
- 1.
比较双方提供的选项
枚举服务器支持的套件和协议版本,并与客户端的密码套件列表对比。curl 的详细输出会显示握手进行到了哪一步、尝试的是哪个版本。
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.
服务器所有者:使用当前推荐的密码套件配置
提供 TLS 1.3 和 TLS 1.2,搭配 ECDHE 和 AEAD 套件(AES-GCM、ChaCha20-Poly1305)。过去十年内的所有客户端都支持它们,现代客户端也都不需要 RSA 密钥交换或 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.
发送正确的服务器名称
在 URL 中使用主机名;或者用 --resolve 强制指定地址,同时保留主机名。使用 openssl s_client 通过 IP 连接时,要传入 -servername。
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.
为双向 TLS 出示客户端证书
如果端点是 mTLS API,就配置客户端证书和密钥;服务器日志会把问题记录为对端证书缺失或无法验证。
bash # Mutual TLS: present the client certificate and key the server expects curl --cert client.pem --key client.key https://mtls.example.com/
相关错误
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)