单向 TLS 只验证服务器身份,客户端通过证书确认「我在和谁通信」。但很多场景还需要服务器验证「正在通信的是谁」:内部服务调用、IoT 设备接入、第三方系统的机对机接口,都要求双方都出示证书。这就是双向 TLS(mTLS)。Nginx 原生支持客户端证书认证,ssl_verify_client 一行配置就能把信任链校验、吊销检查与身份透传串起来。本文在基础 HTTPS 之上深入 TLS 进阶实践,讲解 mTLS 的完整配置、证书轮换流程、TLS 1.3 的优化点与安全基线。
一句话总结: mTLS 让服务器也校验客户端证书,Nginx 用
ssl_verify_client与ssl_client_certificate即可实现,并把证书身份透传给后端。
1. TLS 握手回顾与进阶基础
一句话总结: TLS 握手协商密钥与身份,进阶实践的关键是理解证书链校验、Session 复用与 TLS 1.3 的 1-RTT/0-RTT 流程。
TLS 握手由若干消息组成:ClientHello、ServerHello、证书交换、密钥协商、Finished。在 TLS 1.2 中需要两次往返(2-RTT)才能开始发送应用数据;TLS 1.3 压缩到一次往返(1-RTT),并支持 PSK 复用下的 0-RTT 快速恢复。理解握手阶段的位置,有助于判断优化手段的作用点。
# 查看一个 HTTPS 站点实际协商的协议与套件
openssl s_client -connect api.example.com:443 \
-servername api.example.com 2>/dev/null | grep -E 'Protocol|Cipher'
Nginx 侧的 TLS 参数分布在 http、server 与 stream 三层。基础配置应当把证书、协议、套件与缓存规划清楚:
server {
listen 443 ssl;
http2 on;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/api.example.com.crt;
ssl_certificate_key /etc/nginx/ssl/api.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets on;
}
证书链的完整性是进阶配置的第一个坑:ssl_certificate 文件应当包含「站点证书 + 中间证书」拼接后的完整链,而不是只填叶子证书。缺了中间证书时,某些客户端(尤其是移动端)会因为无法补全链而握手失败,而桌面浏览器因为有缓存反而表现正常,这类问题极具迷惑性。
2. 客户端证书认证与 mTLS 原理
一句话总结: mTLS 在标准握手基础上增加一次客户端证书请求与校验,信任链由 CA 证书、证书扩展与吊销检查共同约束。
单向 TLS 中,服务器发送自己的证书;mTLS 中,服务器在握手时用 CertificateRequest 消息要求客户端也出示证书。客户端把证书与签名发回,服务器用预置的 CA 列表校验。校验通过意味着「持有合法证书且证书对应的私钥未被泄露」——这是 mTLS 比 API Key 更强的根本原因:API Key 是静态秘密,可被复制;证书私钥理论上只存在于客户端。
# 开启客户端证书认证的最小配置
server {
listen 443 ssl;
server_name internal.example.com;
ssl_client_certificate /etc/nginx/ssl/ca-chain.crt; # 信任的 CA 列表
ssl_verify_client on; # 强制校验客户端证书
ssl_verify_depth 2; # 校验链最大深度
location / {
proxy_pass http://backend;
# 把客户端证书的 Subject 透传给后端
proxy_set_header X-Client-CN $ssl_client_s_dn;
proxy_set_header X-Client-Verify $ssl_client_verify;
}
}
校验结果通过内置变量暴露给配置与后端:$ssl_client_verify 为 SUCCESS 表示校验通过,FAILED 或 NONE 表示失败或未提供;$ssl_client_s_dn 是客户端证书的 Subject DN,$ssl_client_serial 是证书序列号。这些变量既可以用于日志审计,也可以用于做更细粒度的授权判断。
| 变量 | 含义 | 典型值 |
|---|---|---|
$ssl_client_verify | 校验结果 | SUCCESS、FAILED、NONE |
$ssl_client_s_dn | 客户端证书 Subject | CN=svc-order,O=Acme |
$ssl_client_i_dn | 签发者 DN | CN=Internal CA |
$ssl_client_serial | 证书序列号 | 2A:3B:... |
$ssl_client_v_start | 证书生效时间 | Oct 1 12:00:00 2026 GMT |
$ssl_client_v_end | 证书过期时间 | Oct 1 12:00:00 2027 GMT |
3. 双向 TLS 的完整配置与授权
一句话总结: 完整 mTLS 由 CA 信任、强制校验、按证书身份授权与审计日志四部分组成,授权通常用 map 或 Lua 完成。
生产环境的 mTLS 不只是「校验一下证书」,还要回答「这个证书能访问哪些资源」。授权策略常见两种:按证书 CN 前缀映射服务(服务间 mTLS),或按证书携带的组织/角色字段映射权限。Nginx 用 map 即可实现第一种:
# 按客户端证书 CN 映射到不同后端
map $ssl_client_s_dn $backend {
default default_backend;
"~CN=svc-order" order_backend;
"~CN=svc-user" user_backend;
"~CN=svc-payment" payment_backend;
}
server {
listen 8443 ssl;
ssl_client_certificate /etc/nginx/ssl/ca-chain.crt;
ssl_verify_client optional; # 允许未携带证书的请求走单独逻辑
location / {
# 校验失败或未提供证书时拒绝
if ($ssl_client_verify != SUCCESS) {
return 403;
}
proxy_pass http://$backend;
proxy_set_header X-Client-CN $ssl_client_s_dn;
}
}
ssl_verify_client 有三档:on 强制校验(无证书即握手失败)、optional 允许客户端选择是否出示、optional_no_ca 接收但不校验(用于先拿到证书信息再决定策略)。optional 模式配合 location 内的 if ($ssl_client_verify != SUCCESS),可以做到「证书有效走 A 逻辑,无效走 B 逻辑」,实现更平滑的降级。
mTLS 的审计很重要。客户端证书的 Subject 与序列号应当写入访问日志,出现安全事件时可以精确追溯到「哪个证书、哪台设备、什么时间调用了哪个接口」:
log_format mtls '$remote_addr - [$time_local] "$request" $status '
'verify=$ssl_client_verify cn=$ssl_client_s_dn '
'serial=$ssl_client_serial';
server {
listen 8443 ssl;
access_log /var/log/nginx/mtls-access.log mtls;
ssl_client_certificate /etc/nginx/ssl/ca-chain.crt;
ssl_verify_client on;
location / {
proxy_pass http://backend;
}
}
4. 证书轮换与生命周期管理
一句话总结: 证书轮换的关键是「先信任新 CA、再签发新证书、最后撤销旧证书」,配合 reload 实现零中断切换。
证书会过期,mTLS 体系里客户端证书、CA 证书、服务器证书都要轮换。轮换的核心原则是「平滑过渡、可回滚」。服务器证书轮换最简单:新证书写入文件后 nginx -s reload,旧连接继续走完,新连接用新证书。
客户端证书体系的轮换要复杂得多。当更换签发新证书的 CA 时,必须先在 Nginx 的信任列表中加入新 CA,保证新旧 CA 签发的证书同时被信任,然后再分批为客户端签发新证书、逐步停用旧证书:
# 轮换期:信任列表同时包含新旧 CA
ssl_client_certificate /etc/nginx/ssl/ca-chain-both.crt;
# ca-chain-both.crt = 新 CA + 旧 CA 拼接
证书签发后的分发与安装也要自动化。企业级方案通常用 Vault 或 cert-manager 管理证书生命周期,Nginx 侧配合 reload 在证书变更后平滑生效。没有自动化的团队至少要做到:证书统一放在固定目录、文件名不含版本、轮换脚本只更新目录内容后 reload,这样回滚也只需替换文件再 reload。
# 生成自签名 CA 与客户端证书(示意)
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout ca.key -out ca.crt -days 3650 -subj "/CN=Internal CA"
openssl req -newkey rsa:2048 -nodes \
-keyout client.key -out client.csr -subj "/CN=svc-order"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out client.crt -days 365
撤销证书也是轮换的一部分。当客户端私钥泄露或证书误发时,需要在 CA 侧吊销(生成 CRL 或启用 OCSP),并让 Nginx 检查吊销状态。OpenSSL 1.1.0+ 默认提供 ssl_crl 指令加载 CRL 文件,配合定时任务刷新:
ssl_client_certificate /etc/nginx/ssl/ca-chain.crt;
ssl_crl /etc/nginx/ssl/ca-crl.pem; # 定期更新吊销列表
ssl_verify_client on;
5. TLS 1.3 特性与性能优化
一句话总结: TLS 1.3 用 1-RTT 与 0-RTT 减少握手往返,Nginx 通过优先套件、Session Ticket 与 OCSP Stapling 释放这些收益。
TLS 1.3 的主要改进是握手提速与加密套件简化。Nginx 1.13.0+ 支持 TLS 1.3,配置 ssl_protocols TLSv1.2 TLSv1.3 后,当客户端也支持时会自动协商到 1.3。TLS 1.3 的密码套件不再用传统命名,直接声明 AEAD 套件即可:
server {
listen 443 ssl;
http2 on;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
# 优先使用 ECDSA 证书可缩短握手;RSA 证书则去掉 ECDSA 套件
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:20m;
ssl_session_timeout 4h;
ssl_session_tickets on;
# OCSP Stapling:由 Nginx 代为查询证书吊销状态,客户端免去额外往返
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
}
TLS 1.3 的 0-RTT 允许客户端在握手的同时发送首个应用请求,但存在重放风险,Nginx 侧通过 ssl_early_data on 显式开启:
server {
listen 443 ssl;
ssl_protocols TLSv1.3;
ssl_early_data on; # 0-RTT
# 0-RTT 请求可能重放,后端需要做幂等处理
proxy_set_header X-Request-ID $request_id;
}
0-RTT 重放防护需要在业务侧配合:只对幂等接口开启 0-RTT,写操作建议关闭或用额外校验。OCSP Stapling 能省掉客户端查询吊销状态的往返,但要求 Nginx 能访问 OCSP 响应服务器,且证书的 Authority Information Access 扩展必须存在。启用后可用 openssl s_client -status 验证。
6. 安全基线与合规检查
一句话总结: TLS 安全基线围绕协议版本、套件强度、证书强度与吊销机制四件事建立,并用外部扫描工具持续验证。
TLS 配置的安全基线是「默认拒绝弱项」:禁用 SSLv3、TLSv1.0/TLSv1.1,套件只保留强 AEAD,证书至少 2048 位 RSA 或 256 位 ECDSA。生产环境还应当关闭存在已知缺陷的能力,并保持证书私钥权限收紧。
# 安全基线配置清单
server {
listen 443 ssl;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers off;
ssl_session_tickets off; # 无状态恢复,避免 Ticket 密钥泄露面
ssl_buffer_size 4k;
# 强制 HSTS,杜绝降级
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}
外部扫描是最可靠的基线验证方式。用 ssllabs 或 nmap 的 ssl-enum-ciphers 对站点做扫描,重点看三项:支持的协议版本是否只含 1.2/1.3;是否存在 !aNULL 之外的弱套件;证书链与吊销信息是否完整。扫描结果应当纳入上线检查清单,任何新 TLS 端点发布前都要过一遍。
| 检查项 | 达标基线 | 常见失败原因 |
|---|---|---|
| 协议版本 | 仅 TLSv1.2/TLSv1.3 | 兼容旧客户端时放宽 |
| 套件强度 | 仅 AEAD,禁 aNULL/MD5 | 老设备需要 DES/RC4 |
| 证书强度 | ≥2048 位 RSA / ECDSA | 历史证书未更新 |
| 证书链 | 含中间证书完整链 | 只填叶子证书 |
| 吊销机制 | OCSP Stapling 或 CRL | 证书无 AIA 扩展 |
| HSTS | 至少 6 个月 max-age | 未开启或缺少 includeSubDomains |
7. 排错与性能权衡
一句话总结: mTLS 排错从握手分层抓取,性能权衡在于握手开销、连接复用与证书校验深度之间取舍。
mTLS 排错最有效的手段是直接看握手过程。openssl s_client 可以精确展示服务器是否请求客户端证书、校验是否通过:
# 不带客户端证书连接,观察服务器是否请求证书
openssl s_client -connect internal.example.com:8443 \
-servername internal.example.com 2>&1 | grep -E 'Acceptable|verify|alert'
# 带客户端证书连接,确认校验结果
openssl s_client -connect internal.example.com:8443 \
-servername internal.example.com \
-cert client.crt -key client.key 2>&1 | grep -E 'verify|Cipher is'
常见错误集中在证书链与吊销两个环节。客户端报 unable to get local issuer certificate,说明 Nginx 的 ssl_client_certificate 中没有签发该客户端证书的 CA;客户端报 certificate has expired,说明证书过期而 Nginx 拒绝;certificate revoked 则说明 CRL/OCSP 判定吊销。逐一核对错误文本即可定位。
性能上 mTLS 的主要成本是握手时的证书链校验与 CA 列表匹配。对于高频机对机调用,开启 ssl_session_cache 与 Session Ticket 可以跳过重复握手;对握手频率极高的场景,还可以用 TLS 1.3 的 0-RTT 进一步省掉一次往返。但省掉的握手越多,重放与密钥泄露的窗口也越大,需要按接口的幂等性与敏感性权衡。
8. 总结
| 环节 | 要点 |
|---|---|
| mTLS 原理 | 服务器在握手时请求客户端证书并按信任链校验 |
| 核心配置 | ssl_client_certificate + ssl_verify_client + ssl_verify_depth |
| 身份透传 | $ssl_client_s_dn 等变量写入日志并转发给后端 |
| 授权策略 | map 按 CN/组织映射后端,optional 模式做降级 |
| 证书轮换 | 先加新 CA 再签发新证书,CRL/OCSP 处理吊销 |
| TLS 1.3 | 1-RTT/0-RTT、新套件命名、OCSP Stapling |
| 安全基线 | 协议、套件、证书强度、吊销、HSTS 五项检查 |
| 排错权衡 | s_client 分层观察握手,缓存与 0-RTT 按需取舍 |
mTLS 是比 API Key 更强的机对机认证方案,Nginx 的 ssl_verify_client 让它落地成本很低:一条 CA 信任列表、一行强制校验、一套变量透传,就能在网关层完成双向认证与审计。把证书轮换与安全基线纳入流程后,TLS 体系才真正可持续。接下来转向静态资源与页面加速,看看传输层之下的性能优化空间。
延伸阅读
- Nginx HTTPS 与 TLS 加固 — 基础证书链与协议套件配置
- Nginx 核心配置结构 — server 块与指令上下文基础
- Nginx 反向代理与负载均衡 — mTLS 后的转发链路配置
- Nginx 日志分析与性能调优 — 访问日志与握手信息联动
- Nginx 限流与安全防护 — 网关层的纵深安全策略
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。