nginx配置client_certificate时400错误主因是ca证书链不完整,ssl_client_certificate需包含根ca与所有中间ca的pem合并文件,缺一不可;optional_no_ca不校验证书签名,mandatory才执行完整x.509验证。
nginx 配置 client_certificate 时证书链不完整导致 400 错误
客户端证书能发过来,但 nginx 直接返回 400 bad request,十有八九是 ca 证书链没配全。nginx 的 ssl_client_certificate 只接受「根 ca 或中间 ca 的 pem 合并文件」,不能只放根证书、漏掉中间层。
- 用
openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -noout检查 PEM 文件里是否包含全部证书(至少两个BEGIN CERTIFICATE块) - 如果客户端用的是 Let’s Encrypt 或 Sectigo 等商业 CA,必须把中间证书和根证书一起写进
ssl_client_certificate指向的文件,顺序无所谓,但缺一不可 - 别用浏览器导出的「证书路径」直接当 PEM —— 浏览器显示的路径是验证链,不是可拼接的 PEM 内容
ssl_verify_client optional_no_ca 和 mandatory 的行为差异
ssl_verify_client 设成 optional_no_ca 不是“跳过验证”,而是让 nginx 接收证书但不校验签名和签发者;设成 mandatory 才真正走完整 X.509 验证流程。很多调试失败是因为误用了前者,以为能拿到证书内容,结果 $ssl_client_cert 是空的或格式异常。
-
optional_no_ca:nginx 不校验证书签名,$ssl_client_cert可能是 base64 编码的 DER 格式(不是 PEM),且无法提取 CN/OU 等字段 -
mandatory:必须通过 CA 链验证,证书有效才继续,此时$ssl_client_cert是标准 PEM 字符串,可用openssl x509 -in /dev/stdin -noout -subject解析 - 调试阶段建议先用
optional_no_ca确认客户端确实在发证书,再切回mandatory开启校验
如何在 proxy_pass 后端服务里安全透传证书信息
后端应用(比如 Python Flask 或 Java Spring Boot)通常不直接处理 TLS 握手,需要 nginx 把验证后的证书字段以 HTTP Header 透传过去。但不能直接透传原始 $ssl_client_cert —— 它体积大、含换行符、可能被篡改。
- 用
ssl_client_verify判断验证结果:if ($ssl_client_verify != SUCCESS) { return 403; } - 用
$ssl_client_s_dn提取标准化的 DN 字符串(如CN=alice,OU=dev,O=acme),它已由 nginx 校验过且无注入风险 - 避免用
$ssl_client_cert或$ssl_client_raw_cert透传 —— 前者含换行符需 base64 处理,后者未验证,攻击者可伪造 - Header 名建议用
X-Client-DN这类明确语义的名称,别用X-SSL-Cert这种模糊叫法
curl 测试双向 TLS 时常见握手失败原因
用 curl --cert client.pem --key client.key --cacert ca.pem https://example.com/ 测试,报 SSL certificate problem: unable to get local issuer certificate 或直接 hang 住,往往不是 nginx 配置问题,而是 curl 自身证书信任链没对齐。
- 确保
--cacert ca.pem与 nginx 的ssl_client_certificate是同一份 PEM(含相同 CA 集合) - client.pem 必须是 PKCS#1 格式的私钥 + PEM 证书合并体,或用
--cert client.crt --key client.key分开指定 - 如果服务端要求 SNI,加
--resolve example.com:443:127.0.0.1强制 IP 绑定,避免 DNS/SNI 不一致 - 加
-v看详细 handshake 日志,重点关注* Server certificate和* Client certificate两段是否都出现
nginx 对客户端证书的解析发生在 SSL 握手阶段,早于任何 location 匹配和 rewrite。这意味着一旦配置错误,请求根本进不了日志或 access_log,只能靠 error_log 中的 SSL_do_handshake() failed 或 no suitable certificate found 这类提示定位。最常被忽略的是:CA 证书文件权限(nginx worker 进程必须可读)、SELinux 上下文(尤其在 CentOS/RHEL 上)、以及 reload 时旧连接未断开导致缓存旧配置。










