Nginx双向SSL认证中OPTIONS预检失败是因TLS握手阶段强制校验客户端证书,而浏览器预检不携带证书;可靠解法是在location内用if判断OPTIONS并返回204,同时保留非OPTIONS请求的ssl_verify_client on强校验。

在开启 SSL 双向认证(mTLS)的 Nginx 环境中,OPTIONS 预检请求默认会失败,因为浏览器或客户端在 CORS 预检阶段不携带客户端证书——这是 TLS 层面的限制,不是 Nginx 配置疏漏。
为什么 OPTIONS 请求常被拒绝
浏览器发起 CORS 预检时,只完成 TCP + TLS 握手的初始协商,不会主动提交客户端证书。而 Nginx 设置了 ssl_verify_client on 后,任何 TLS 连接都强制校验客户端证书,导致预检连接在握手阶段就被终止,返回 400 或直接断连。
让 OPTIONS 请求绕过证书校验的两种可靠方式
-
按 location 分离处理预检路径:对特定 API 路径(如
/api/)下的OPTIONS请求单独配置,关闭证书验证 -
用 if 判断方法并临时关闭校验:在 server 块中通过
if ($request_method = 'OPTIONS') { ... }拦截,配合ssl_verify_client off和快速响应
推荐的 location 分离配置示例
更安全、更清晰的做法是把预检和业务请求拆开:
server {
listen 443 ssl;
ssl_certificate /path/to/server.crt;
ssl_certificate_key /path/to/server.key;
ssl_client_certificate /path/to/ca.crt;
ssl_verify_client on;
ssl_verify_depth 2;
<pre class="brush:php;toolbar:false;"># 正常业务接口(强制双向认证)
location /api/ {
proxy_pass http://backend;
proxy_set_header X-Client-DN $ssl_client_s_dn;
# 其他 proxy 设置...
}
# 专用于预检的 location,不校验客户端证书
location /api/ {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
add_header Access-Control-Allow-Credentials "true";
add_header Access-Control-Max-Age "86400";
add_header Content-Length "0";
add_header Content-Type "text/plain; charset=utf-8";
return 204;
}
}}
注意:上面写法有重复 location 的风险,实际应合并为一个 location 块内判断,或改用如下更稳妥结构:
location /api/ {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Access-Control-Allow-Credentials "true";
add_header Access-Control-Max-Age "86400";
add_header Content-Length "0";
return 204;
}
<pre class="brush:php;toolbar:false;"># 非 OPTIONS 请求才走双向认证流程
ssl_verify_client on;
proxy_pass http://backend;
proxy_set_header X-Client-DN $ssl_client_s_dn;}
不建议使用的“伪解决方案”
避免使用 ssl_verify_client optional + if ($ssl_client_verify != SUCCESS) { return 204; }:这会导致未认证客户端也能通过 OPTIONS,且无法区分真实预检与恶意试探;更严重的是,$ssl_client_verify 在未发送证书时为空,逻辑易出错,审计日志也失去意义。
本质上,这不是 Nginx 的缺陷,而是 HTTPS 协议层与 CORS 机制的天然冲突。正确做法是明确区分“握手前预检”和“握手后通信”,让预检走轻量通道,主业务走强认证通道。











