nginx 仅在 ssl_verify_client optional 模式下可记录 ssl 客户端证书验证失败(failed:reason)和未提供证书(none)的日志,需结合 $ssl_client_verify、$ssl_client_s_dn 等变量结构化输出,并通过 debug 日志和后端 x-header 协同实现完整审计闭环。

Nginx 无法在 access_log 中直接记录所有 ssl_verify_client 失败的请求,因为部分失败发生在 TLS 握手阶段,HTTP 请求根本未生成。但针对已进入 Nginx 处理流程但验证失败的情形(如 optional 模式下客户端提供了无效证书),可通过日志+变量组合实现有效审计。
必须启用 ssl_verify_client optional(而非 on)
ssl_verify_client on 会在验证失败时直接中断 TLS 握手,返回 400 错误,此时 Nginx 不产生 access_log 条目。只有设为 optional,Nginx 才会接收请求、执行验证、记录结果,并允许你在日志中捕获失败状态:
- 客户端未提供证书 →
$ssl_client_verify = NONE - 客户端提供了证书但验证失败(过期、CA 不信任、签名错误等)→
$ssl_client_verify = FAILED:reason - 验证通过 →
$ssl_client_verify = SUCCESS
因此,审计目标应聚焦于 FAILED 和 NONE 这两类可记录的异常情形。
在 log_format 中结构化记录验证结果与上下文
仅记录 $ssl_client_verify 不足以定位问题。需搭配关键字段,形成可分析的审计线索:
-
$ssl_client_verify:核心状态,区分 SUCCESS / FAILED:xxx / NONE -
$ssl_client_s_dn:即使失败也常存在(只要客户端发了证书),用于识别来源身份 -
$ssl_protocol和$ssl_cipher:辅助判断是否因协议降级或弱加密套件导致握手异常 -
$request和$status:确认是否进入业务路径(如 403 常见于后端拒绝无证/验签失败请求) -
$http_user_agent:筛查扫描器(如curl/7.58.0,python-requests,sqlmap)
示例配置:
log_format client_auth_audit '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_user_agent" '
'verify="$ssl_client_verify" '
'dn="$ssl_client_s_dn" '
'proto="$ssl_protocol" cipher="$ssl_cipher"';
access_log /var/log/nginx/client-auth-fail.log client_auth_audit;
该日志将清晰呈现每次请求的验证决策链,便于 grep 或 ELK 分析 FAILED: 或 NONE 条目。
配合 error_log debug 级别定位深层失败原因
$ssl_client_verify 中的 FAILED:reason 有时信息简略(如 FAILED:certificate has expired),更详细的错误来自 OpenSSL 底层。临时启用 debug 日志可补全上下文:
error_log /var/log/nginx/ssl-debug.log debug;
重启后,观察日志中类似以下条目:
SSL_do_handshake() failed (SSL: error:1417C0C5:SSL routines:tls_process_client_certificate:certificate verify failed)
这类输出能明确指向是证书过期、吊销、不匹配域名,还是 CA 链断裂,是排查 FAILED 根源的关键依据。
后端协同过滤:用 X-Header 传递验证结果并做二次拦截
Nginx 日志只记录“发生了什么”,真正阻止非法访问需靠逻辑闭环。建议在 location 中透传验证状态,并由后端服务拒绝 FAILED 或 NONE 请求:
proxy_set_header X-Client-Verify $ssl_client_verify; proxy_set_header X-Client-DN $ssl_client_s_dn;
后端收到 X-Client-Verify: FAILED:unable to get local issuer certificate 时,可立即返回 403,同时该 403 响应会被 access_log 记录,形成“失败请求 → Nginx 记录 → 后端拦截 → 日志留痕”的完整审计回路。
不复杂但容易忽略











