Nginx TLS 进阶与 mTLS:客户端证书认证、证书轮换与 TLS 1.3 优化

在基础 HTTPS 之上深入 TLS 进阶实践,覆盖客户端证书认证与双向 TLS、ssl_verify_client 的完整配置、证书轮换与生命周期管理、TLS 1.3 特性与性能优化,以及安全基线与排错方法。

单向 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客户端证书 SubjectCN=svc-order,O=Acme
$ssl_client_i_dn签发者 DNCN=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.31-RTT/0-RTT、新套件命名、OCSP Stapling
安全基线协议、套件、证书强度、吊销、HSTS 五项检查
排错权衡s_client 分层观察握手,缓存与 0-RTT 按需取舍

mTLS 是比 API Key 更强的机对机认证方案,Nginx 的 ssl_verify_client 让它落地成本很低:一条 CA 信任列表、一行强制校验、一套变量透传,就能在网关层完成双向认证与审计。把证书轮换与安全基线纳入流程后,TLS 体系才真正可持续。接下来转向静态资源与页面加速,看看传输层之下的性能优化空间。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「nginx」更多文章

  1. Nginx Ingress Controller:Kubernetes 云原生网关的路由、证书与金丝雀发布
  2. Nginx 监控与可观测性:stub_status 指标、Prometheus 集成与容量规划
  3. Nginx 静态资源与页面加速:零拷贝、缓存验证与 Brotli 压缩联动