Nginx启用TLS双向认证返回400错误,本质是未在CertificateRequest中声明信任的CA,导致Go/Java等严格客户端跳过证书发送;需检查error.log确认Nginx拦截,确保ssl_client_certificate配置完整CA链且ssl_verify_client设为mandatory。

遇到 Nginx 启用 TLS 双向认证后返回 400 Bad Request: No required SSL certificate was sent,本质不是客户端“没发”,而是服务端没让客户端知道该发——Nginx 未正确向客户端声明它信任哪些 CA,导致 Go、Java 等严格遵循 TLS 协议的客户端直接跳过证书发送步骤。
确认是 Nginx 主动拒绝,而非后端返回
先查 Nginx 错误日志(如 /var/log/nginx/error.log),搜索对应请求时间点。若出现以下任一记录,说明是 Nginx 自身拦截:
- "No required SSL certificate was sent" —— 明确握手阶段缺失客户端证书
- "SSL_do_handshake() failed" 或 "SSL_read() failed" —— TLS 层异常中断
- 无任何 upstream 访问日志,且 access.log 中状态码为 400 —— 请求未到达后端
检查 ssl_client_certificate 是否提供完整 CA 链
Nginx 的 ssl_client_certificate 指令必须指向一个 PEM 文件,该文件需包含:
- 所有用于验证客户端证书的根 CA 和中间 CA 证书(不含客户端自己的证书)
- 每个证书块以 -----BEGIN CERTIFICATE----- 开头、-----END CERTIFICATE----- 结尾
- 文件使用 Unix 换行(LF),无 BOM、无空行、无多余空格
验证方法:
- 运行 openssl crl2pkcs7 -nocrl -certfile /path/to/ca-bundle.pem | openssl pkcs7 -print_certs -noout,输出应至少含两个证书
- 若只看到一个证书,说明链不全;若报错 "unable to load PKCS7 object",说明格式非法
确认 ssl_verify_client 设置为 mandatory
避免误用 optional_no_ca:
- ssl_verify_client optional_no_ca:Nginx 接收证书但不校验签名和签发者,$ssl_client_cert 可能是 DER 格式,无法提取 CN 字段,也不触发标准 CertificateRequest
- ssl_verify_client mandatory:强制走完整 X.509 验证流程,此时 Nginx 才会把 ssl_client_certificate 中的 CA 列表填入 TLS 握手的 certificate_authorities 字段
调试建议:先临时设为 optional_no_ca,curl 加 -v 观察是否收到 CertificateRequest;确认客户端能发后再切回 mandatory 并启用验证。
排除客户端行为干扰
curl 能通而 Go 程序失败,是典型信号:
- curl 默认兼容性较强,可能在未收到 CA 列表时仍尝试发送证书;Go 的 crypto/tls 严格按 RFC 执行,不发就是不发
- 不要依赖 curl 成功就认为配置没问题——它掩盖了协议层面的缺陷
- 可用 openssl s_client -connect example.com:443 -servername example.com -cert client.crt -key client.key -CAfile ca.crt -debug 抓握手过程,查看 ServerHello 后是否出现 CertificateRequest 及其 certificate_authorities 内容


















