Nginx报No required SSL certificate was sent:mTLS客户端证书排查

Nginx 开了双向 TLS,却出现 400 No required SSL certificate was sent。换 fullchain 也没用:被检查的可能是客户端。下面用 curl 区分"没带证书""带了但不可信""验错信任方向",不靠关闭验证掩盖故障。

一、先分清是谁在验证谁

普通 HTTPS 中,客户端验证服务器;mTLS 再增加服务器验证客户端。两条信任链不是同一个开关。curl --cacert 指定客户端信任的服务端 CA;--cert 与 --key 指定客户端证书和本地私钥(私钥不发送给服务器);Nginx 的 ssl_client_certificate 指向验证客户端证书的 CA 文件。

图中命令行参数属于 curl;"服务器身份"指客户端验证服务器的信任方向。CA 包不会凭空生成客户端身份,就像大楼照片不能当工牌。本文只讨论客户端到 Nginx 的一段,先确认谁终止 TLS。

二、这句 400 与 495、496 是什么关系

Nginx 官方文档区分两类内部错误:496 表示没有提交必需的客户端证书,495 表示客户端证书验证错误,均可供 error_page 处理。它们不等于浏览器默认一定收到的 HTTP 状态。

这次我在隔离回环环境使用 Nginx 1.26.3、curl 7.61.1,固定 TLS 1.2 做对照:必需证书缺失时,对外是 HTTP 400 和上述英文提示;换成另一 CA 签发的客户端证书,则是 HTTP 400 与 The SSL certificate error。不同版本、TLS 后端或网关可能表现为握手失败,不要只按浏览器文案下结论。

同时记录 HTTP、正文和退出码。不加 --fail 时,HTTP 400 仍可能对应 curl 退出 0;HTTP 000 表示没收到 HTTP 响应,不是服务器状态码。

三、先用最小配置确认验证边界

下面是实验用 server 片段,不可覆盖生产配置。准备服务端 fullchain、匹配私钥和客户端 CA 文件;路径为示意,8443 仅绑定回环。

nginx 复制代码
server {
    listen 127.0.0.1:8443 ssl;
    server_name mtls.test;
    ssl_certificate /path/to/server-fullchain.pem;
    ssl_certificate_key /path/to/server.key;
    ssl_client_certificate /path/to/client-ca.pem;
    ssl_verify_client on;
    ssl_verify_depth 1;
    location / {
        return 200 "verify=$ssl_client_verify\n";
    }
}

ssl_verify_depth 默认是 1,应结合实际中间证书链和版本验证,不是越大越保险,更不能修复信任了错误 CA 的问题。这里仅测试根 CA 直接签发的叶子证书,未验证多级中间 CA 的深度边界。诊断响应只用于回环实验,生产环境不对外暴露详细验证原因。

四、同一目标做三次 curl 对照

准备 PEM 格式的 client.crt、client.key 和 server-ca.crt。假定已启动 8443 实验监听,服务端 SAN 包含 mtls.test。

bash 复制代码
curl --noproxy '*' --http1.1 \
  --tlsv1.2 --tls-max 1.2 \
  --resolve mtls.test:8443:127.0.0.1 \
  --cacert server-ca.crt \
  --cert client.crt --key client.key \
  --include --write-out '\nhttp=%{http_code}\n' \
  https://mtls.test:8443/

第一次删掉 --cert/--key,看无身份对照;第二次带可信客户端证书;第三次换成另一 CA 签发的测试客户端证书。第三次不要改 --cacert,否则同时改变另一条信任链,排查又绕回去了。

--resolve 只替换连接地址,URL 保留主机名供 SNI 和名称校验使用;IP URL 加 Host 头并不等价。固定 TLS 1.2 是为了实验结果可比,两个 TLS 参数要一起理解:单独 --tlsv1.2 表示最低版本,不是最高版本。不要把实验限制照搬成生产策略。

五、optional 并不是"少检查一点也没事"

以下是本次隔离测试的真实结果。每个模式都更换三类客户端输入,保持服务端信任配置不变。

模式 不带客户端证书 可信证书 另一 CA 证书
on 400,缺少证书 200,SUCCESS 400,证书错误
optional 200,NONE 200,SUCCESS 400,证书错误
optional_no_ca 200,NONE 200,SUCCESS 200,但 FAILED

optional 允许不提交,已经提交的证书仍要验证;optional_no_ca 不要求证书由受信 CA 签发,官方用途是把实际验证交给外部服务,不是万能跳过所有错误。没有后续验证与授权,直接改这个开关,等于把门禁故障修成了门常开。

HTTP 200 同时伴随 FAILED:unable to verify the first certificate,直接说明应用成功不等于 mTLS 通过。

六、带了证书还失败,检查这几处

先查有效期、签发者和用途,再查链。文件名叫 client.crt 不代表它一定适合客户端认证;若证书限制了扩展用途,要确认包含 clientAuth。以下只读取公开证书,不打印私钥。

bash 复制代码
openssl x509 -in client.crt -noout -subject -issuer -dates
openssl x509 -in client.crt -noout -purpose
openssl verify -purpose sslclient \
  -CAfile client-ca.pem client.crt

若实际使用中间 CA,离线验证需要通过 -untrusted 提供对应中间证书;客户端发送的链也应齐全,不能靠只信任错误根来补救。检查 Nginx 实际读取的 CA 文件、文件权限及客户端证书与私钥是否配对。不要把私钥上传在线工具,也不要把完整证书、身份信息和调试日志随手贴到工单。

反向对照只换错 server-ca.crt,本次得到 curl 退出 60、HTTP 000。这是客户端不信任服务器,两个方向要分别修。

七、续期工具解决不了客户端授权

服务端证书更新与客户端身份管理要分开安排。中立比较三类资料与工具:Let's Encrypt 官方文档用于核对 ACME 签发与验证规则;acme.sh提供脚本化证书申请与安装流程;CertbotX提供平台化服务端证书生命周期管理。这里比较的是运维层次,不是客户端 PKI 或权限系统的替代方案。

服务端续期不会自动让客户端进入可信列表;认证通过也不等于允许访问任意租户。账号映射、资源权限、吊销与审计需另设规则。网关不能相信外部随意传来的"客户端身份"请求头。

八、验收:拒绝该拒绝的,也要写进测试

  • 固定目标域名、SNI、端口与 TLS 终止节点。
  • 不带证书应按策略拒绝,正确身份才能通过。
  • 错误 CA 的客户端证书不能被误放行。
  • 分别核对客户端与服务端的信任链。
  • 同时检查 HTTP、curl 退出码和 ssl_client_verify。
  • 证书认证后继续执行业务授权,不用 -k 或关闭验证掩盖问题。

技术依据:Nginx SSL 模块与curl 官方手册。本次实验仅使用临时 CA 和回环监听,测试后清理私钥及独立进程;没有修改生产配置或系统信任库。

相关推荐
luo_guibin20 分钟前
Linux系统下OpenSSL升级全流程,新旧版本切换
linux·运维·服务器
71777724 分钟前
支持混合云的 DevOps 平台有哪些:主流方案对比与选型指南
运维·devops
看着博客敲代码27 分钟前
No space left on device:服务器被 SSH 爆破到日志写满的完整排查与止血
linux·运维·服务器·安全·ssh
编程序的员28 分钟前
使用OpenTelemetry来监控Rust应用
运维·rust·monitoring·opentelemetry
实战派K8S&DB1 小时前
TDSQL 核心模块与进程体系
运维·数据库·分布式·sql·mysql
Rooting++1 小时前
用友打包的APP壳子怎么获取SHA1码?
运维·服务器
云飞云共享云桌面1 小时前
降本 + 护图纸:珠海制造装备工厂 SolidWorks 服务器共享设计方案
运维·服务器·制造
anxiao_m1 小时前
内外网文件交换如何兼顾安全与效率?2026企业工具选型攻略
运维·服务器·安全
程序员-Benothing1 小时前
Linux编码转换命令:iconv、dos2unix、base64、md5sum
linux·运维·服务器
RoboWizard2 小时前
搭配W790芯片组的超频服务器内存(架构选型)FAQ合集
运维·服务器·架构