症状
nginx 的消息精确得可贵:“No required SSL certificate was sent” 表示服务器要求了(ssl_verify_client on)而客户端什么都没出示——客户端配置缺口;孪生兄弟 “The SSL certificate error” 表示证书到了但验证失败——CA 错、过期或链深超限。负载均衡侧表现为握手期以 certificate_required 告警关闭连接。同一凭证部分环境过部分环境挂,几乎总是 CA 信任或链完整性差异。亲手驱动握手才能看到服务器视角的协商:openssl s_client 配 client 证书连接,grep Verify/depth/error 即得判决——Verify return code 0 为成功,self-signed 或 expired 直接点名。
诊断思路
先用 s_client 复现握手拿判决码与错误类别;再核对服务端策略——ssl_verify_client 是 on 还是 optional、ssl_client_certificate 信任链与客户端 issuer 是否一致、x509 查证书有效期;最后对比能过与不能过的客户端证书 issuer,不同签发者即一个 CA 受信另一个不受信的供给问题。SNI 或按路径的差异化 mTLS 把请求路由到要求另一种证书的 server 块,是隐蔽变体。
根因
双向 TLS 要求客户端出示服务器信任的证书。缺口不同、失败层不同:未发证书(客户端配置)、签发者不受信(CA 失配)、凭证过期、链不完整超出 verify depth——各自产生不同的诊断签名。
修复与验证
客户端在 mTLS 上下文配置证书与私钥;服务端信任对齐到签发 CA;到期的客户端证书轮换并加监控(提前告警);发送完整链并把 ssl_verify_depth 设够覆盖;部分强制场景用独立 server 块或 optional 验证加应用层校验——optional 没有应用层检查会静默放过无证书请求,强制必须集中一处且有意为之。验证时 openssl 握手以 Verify return code 0 完成、API 调用成功、未认证客户端被拒且错误与文档一致。
代码示例
400 No required SSL certificate was sent
400 The SSL certificate error
openssl s_client -connect api.example.com:443 \
-servername api.example.com \
-cert client.pem -key client.key \
</dev/null 2>&1 | grep -E "Verify|depth|error"
# Verify return code: 0 (ok) <- success
# verify error: self-signed certificate in certificate chain
# verify error: certificate has expired
server {
listen 443 ssl;
server_name api.example.com;
ssl_client_certificate /etc/nginx/ca/client-ca.pem;
ssl_verify_client on; # or optional + app-level check
ssl_verify_depth 2; # cover the intermediate chain
}
# verify the client pair against the same CA the server trusts:
openssl verify -CAfile /etc/nginx/ca/client-ca.pem client.pem
openssl s_client -connect api.example.com:443 \
-cert client.pem -key client.key </dev/null 2>&1 | grep Verify