双向TLS认证(mTLS)在微服务内部通信、API网关鉴权场景里用得越来越多。配置时大多数人知道要开启ssl_verify_client,却容易忽略另一个关键参数ssl_verify_depth。它的默认值是1,一旦客户端证书不是直接由根CA签发,而是经过中间CA签发,验证就会失败,Nginx日志里会看到类似"certificate chain too long"的错误。要理解这个问题,得先弄清楚证书链的验证机制。

证书链的结构与验证原理
一张客户端证书通常不是凭空存在的。终端实体证书(也就是颁发给用户或设备的那张)由中间CA签发,中间CA可能又由上级中间CA签发,最顶层的根CA则是自签名的。OpenSSL在验证客户端证书时,会从终端证书开始,沿着签发者的路径一路向上找,直到遇到一个自己信任的根证书为止。这条路径上的每一跳,就是链上的一个节点。
Nginx收到客户端证书后,会把证书连同客户端在TLS握手时附带的中间链一起交给OpenSSL验证。ssl_verify_depth限制的正是这条构建出来的链的最大长度。这里有个非常容易踩的坑:深度的计数方式。深度并不是简单地数中间CA的个数,而是指链上证书的数量减一,或者说从终端证书到信任锚之间的跳数。一张直接由根CA签发的证书,链深度是0;由一级中间CA签发的证书,深度是1;两级中间CA则是2。而Nginx的默认值恰好是1,意味着它只接受"根CA直接签发"或"一级中间CA签发"这两种情况,再长就直接拒绝。
举个例子,某个企业内部的证书体系是这样的:内部根CA签发了一个中间CA,这个中间CA又签发了业务CA,业务CA给客户端签发终端证书。这条链是"终端证书 -> 业务CA -> 中间CA -> 根CA",共四张证书,深度为3。如果ssl_verify_depth还是默认的1,验证必然失败,即使客户端把完整的中间链都发过来了也一样。
如何确定正确的ssl_verify_depth取值
确定取值最直接的办法是查看实际的证书链。用OpenSSL命令可以一步到位地打印出链的层级:
openssl verify -show_chain -CAfile ca-chain.pem client.crt
输出会逐行列出链上的每一张证书,从终端证书到根证书,数一下行数再减一,就是需要的最小深度。也可以用下面这个命令直接查看客户端证书的签发者路径:
openssl x509 -in client.crt -noout -issuer -subject
实际配置时有一个经验法则:取值为你的证书链中中间CA的数量。如果终端证书由两级中间CA签发,配置ssl_verify_depth 2即可。为了给未来留余量,比如企业后续可能增加一层业务CA,也可以适当放大一到两档。但不要无脑写一个很大的数字,深度开得过大意味着Nginx会接受更长的链,攻击者构造一条嵌套很深的恶意证书链时,验证计算量会增加,同时链上任何一环的管控松懈都会被放大。一般建议不超过3到5。
还有一个隐蔽的问题需要注意:客户端是否发送了完整的中间链。TLS握手时客户端可以选择只发终端证书,把中间证书的获取留给服务端。如果客户端没发中间链,而服务端的CA信任列表里又没有对应的中间CA,那么无论深度配多大都验证不过。这种情况下要么让客户端发送完整链(比如curl的--cert配合完整链的pem文件),要么在Nginx的ssl_client_certificate指向的文件里包含中间CA证书。
完整配置示例与常见报错排查
下面是一个开启了双向认证的典型Nginx server配置:
server {
listen 443 ssl;
server_name api.ippipp.com;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# 双向认证:要求并验证客户端证书
ssl_verify_client on;
# 信任的CA链,包含根CA和所有中间CA
ssl_client_certificate /etc/nginx/ssl/ca-chain.pem;
# 链深度:允许"终端证书 + 两级中间CA"
ssl_verify_depth 2;
location / {
proxy_pass http://backend;
proxy_set_header X-Client-DN $ssl_client_s_dn;
}
}配置修改后记得用nginx -t检查语法再reload。排查验证失败时,重点看Nginx的error日志。如果出现"client SSL certificate verify error: unable to get local issuer certificate",说明链上缺中间CA,问题在信任文件而不是深度;如果出现"certificate chain too long"或者验证结果码是深度相关错误,才需要调整ssl_verify_depth。还可以借助$ssl_client_verify变量做诊断页面,返回具体的验证状态。
另外提醒一点,不同版本Nginx对这个参数的行为略有差异。老版本中深度值在某些边界情况下的计算和OpenSSL不完全一致,如果升级Nginx后突然出现验证行为变化,优先检查这个参数的语义是否受影响,同时确认OpenSSL版本没有大的变更。调试阶段可以临时开启error_log的debug级别,能完整看到TLS握手过程中证书链的验证轨迹,比盲猜配置高效得多。
总结一下:证书链深度不是玄学,数清楚链上中间CA的层数,配置对应的ssl_verify_depth,保证ca-chain.pem包含完整的信任链,双向认证的证书验证问题基本都能迎刃而解。
Nginxssl_verify_depth证书链修改时间:2026-09-07 16:16:39