1. 场景复现:你的HTTPS请求为何突然"不信任"了?
假设你正在为电商系统搭建微服务架构,用OpenResty作为API网关反向代理到支付服务。某天突然收到报警:"SSL certificate verify failed"。小张的配置文件是这样写的:
# 错误示例:未配置证书验证
server {
listen 443 ssl;
server_name gateway.example.com;
ssl_certificate /path/to/gateway.crt;
ssl_certificate_key /path/to/gateway.key;
location /payment/ {
proxy_pass https://internal-payment-service/;
# 缺少关键安全配置 ↓
}
}
当请求经过这个网关转发时,OpenResty默认不会验证后端证书的有效性。这就好比快递员不核对收件人身份证,直接把包裹交给陌生人。当后端服务证书过期、域名不匹配或被中间人攻击时,系统就会毫无防备。
2. 核心配置参数解析:安全验证三剑客
2.1 proxy_ssl_verify
proxy_ssl_verify on; # 启用证书验证(默认off)
proxy_ssl_verify_depth 2; # 证书链验证深度
- 深度验证示例:假设证书链是
服务端证书 <- 中间CA <- 根CA
,depth=2表示允许两级CA链
2.2 proxy_ssl_trusted_certificate
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; # 信任的CA集合
- 文件需要包含所有可能用到的CA证书
- 建议将企业私有CA也加入此文件
2.3 proxy_ssl_server_name
proxy_ssl_server_name on; # 启用SNI扩展(应对多域名托管场景)
3. 完整示例演示:从错误到正确的配置进化
3.1 基础安全配置
server {
listen 443 ssl;
server_name gateway.example.com;
# 网关自身证书
ssl_certificate /etc/ssl/gateway.crt;
ssl_certificate_key /etc/ssl/gateway.key;
location /payment/ {
proxy_pass https://internal-payment-service/;
# 安全配置三件套
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /etc/ssl/trusted-cas.crt;
proxy_ssl_server_name on;
# 调试日志(生产环境建议关闭)
proxy_ssl_session_reuse on;
error_log /var/log/nginx/ssl_errors.log debug;
}
}
3.2 进阶场景:私有CA认证
当后端服务使用自签名证书时,需要将私有CA加入信任链:
# 合并公有CA和私有CA
cat /etc/ssl/certs/ca-certificates.crt /path/to/private-ca.crt > /etc/ssl/trusted-cas.crt
3.3 证书验证失败时的Nginx日志解读
观察错误日志时,你可能会看到:
SSL_do_handshake() failed (SSL: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed)
这通常意味着:
- 证书链不完整(缺少中间CA)
- 证书已过期
- 域名不匹配
- 根CA未受信任
4. 关联技术与进阶方案:Lua脚本动态处理
当需要动态适配多套证书时,可以通过Lua脚本扩展:
location /dynamic-proxy/ {
access_by_lua_block {
local upstream = require "ngx.upstream"
local hosts = {
["serviceA"] = { ca = "/path/ca1.crt" },
["serviceB"] = { ca = "/path/ca2.crt" }
}
local service_name = ngx.var.arg_service
local ca_path = hosts[service_name].ca
-- 动态设置信任证书
ngx.var.proxy_ssl_trusted_certificate = ca_path
}
proxy_pass https://backend/;
}
5. 应用场景与选型建议
适用场景:
- 混合云架构中对接不同CA签发的服务
- 微服务间的mTLS通信
- 代理第三方HTTPS API时的安全验证
技术对比:
方案 | 优点 | 缺点 |
---|---|---|
Nginx原生配置 | 性能最优,配置简单 | 缺少动态能力 |
Lua脚本扩展 | 灵活应对复杂场景 | 需要维护脚本逻辑 |
第三方鉴权服务 | 集中管理证书 | 增加网络延迟 |
6. 避坑指南:那些年我们踩过的证书坑
6.1 证书链不完整
错误现象:unable to get local issuer certificate
解决方法:
# 使用openssl检查证书链
openssl s_client -connect internal-payment-service:443 -showcerts
6.2 时钟不同步引发的"过期"
某次故障排查发现,虽然证书有效期到2025年,但网关服务器的BIOS时钟错误导致提前触发过期验证。
6.3 隐蔽的SAN扩展
当证书没有包含Subject Alternative Name时,即使Common Name正确也会验证失败。
7. 总结与最佳实践
通过本文的配置示例和故障分析,我们总结出以下经验:
- 强制验证:生产环境必须开启proxy_ssl_verify
- 证书管理:
- 使用统一的证书仓库
- 设置自动续期提醒
- 监控体系:
# 定时检查证书有效期 echo | openssl s_client -connect backend:443 2>/dev/null | openssl x509 -noout -dates
- 防御性编程:在Lua脚本中加入异常捕获
- 文档同步:每次证书变更都要更新对应配置说明
当你在OpenResty的证书验证之路上遇到问题时,不妨从这三个维度切入检查:
- 信任链:是否包含所有必要CA证书?
- 时间线:证书是否在有效期内?
- 身份认证:证书域名与请求目标是否匹配?
记住,良好的证书管理习惯就像系安全带——平时可能觉得麻烦,关键时刻能救命。