ssl_verify_client on 是金融级mTLS强制校验开关,缺失/无效证书时在TLS握手层返回400或495,必须配合ssl_client_certificate、ssl_verify_depth、ssl_crl等实现闭环验证。

ssl_verify_client 是 Nginx 实现金融级双向 TLS(mTLS)的开关核心,它不只决定“要不要验”,更直接控制认证失败时的响应码、握手阶段拦截位置和整体安全水位。生产环境必须设为 on,设为 optional 或 off 意味着放弃客户端身份校验,与金融级要求根本冲突。
ssl_verify_client 的三种取值与真实影响
它不是简单的布尔开关,而是三态策略控制器:
- off:完全禁用客户端证书验证,Server Hello 中不会发送 Certificate Request,等同于单向 TLS
- optional:服务端发出 Certificate Request,但允许客户端不提供证书或提供无效证书;Nginx 仍完成握手,$ssl_client_verify 值为 FAILED/NONE,后端需自行判断——这在金融场景中不可接受,因无法从网络层阻断非法请求
- on:强制要求客户端提供有效证书;若缺失、格式错误、签名失败、过期或不在信任链内,Nginx 在 TLS 握手阶段立即终止连接,返回 HTTP 状态码 400(Bad Request)或 495(SSL Certificate Error),不进入应用层
为什么设为 on 后常返回 495 而不是预期的成功?
这不是配置“没生效”,而是 Nginx 在 TLS 层已严格执行了校验逻辑。常见硬性卡点有:
- ssl_client_certificate 指向的 ca.crt 文件含 BOM、空行、多余空格或被文本编辑器自动转义——OpenSSL 解析失败即拒收整个链
- 客户端证书不是由该 CA 直接签发(例如用了二级中间 CA,但 ssl_verify_depth 仍为 1)
- 客户端证书 extendedKeyUsage 缺失 clientAuth(虽 Nginx 不强制检查,但部分合规审计工具会报错)
- 证书已过期、吊销,且未配置 ssl_crl 或 CRL 文件本身过期/格式错误
配合 ssl_verify_client on 必须同步落地的关键项
单独打开 on 只是起点,金融级闭环需以下协同:
- 显式透传身份:用 proxy_set_header X-Client-DN $ssl_client_s_dn 将原始 Subject 字符串(如 CN=core-banking-prod,OU=backend)送至后端,供白名单匹配
- 明确失败拦截:配置 error_page 495 496 = @deny_access,统一返回简洁 403,避免泄露证书错误细节
- 启用吊销检查:ssl_crl /path/to/crl.pem 并确保定期更新,否则过期/吊销证书仍能通过基础链验证
- 限定协议与套件:仅启用 TLSv1.2/TLSv1.3,禁用所有弱算法,防止降级攻击绕过 mTLS
上线前必须验证的三个信号
确认 mTLS 已真正生效,不能只看 curl 是否通:
- 响应头中出现 X-Client-Verify: SUCCESS,证明 Nginx 完成完整校验流程
- 抓包可见 TLS 握手阶段 Server Hello 后紧接 Certificate Request,且 Client Certificate 消息存在
- 用错误证书请求时,返回 495 或 496(非 403 或 500),说明拦截发生在 TLS 层而非业务层











