一、信任链结构
叶子证书(depth 0)绑域名,服务器必须下发;中间证书(depth 1)承接信任传递,必须随叶子一起下发;根证书(depth 2)预装在客户端信任库,通常不下发。链上缺任何一级,验证即断。
判定依据: 浏览器内置 AIA(Authority Information Access)补链机制,链不完整时会按证书内的地址自动下载中间证书后继续验证,且该动作不留日志;脚本、SDK、移动端网络层绝大多数不做补链,缺一级即失败。两条客户端路径的差异即「浏览器打得开、脚本打不开」的全部机制。
二、四类成因判别表
| 成因 | 现象特征 | 责任方 |
|---|---|---|
| 只下发了叶子证书 | 诊断只看到 1 条链;浏览器正常、命令行客户端失败 | 对端:拼接 fullchain 重新加载 |
| 链拼接顺序反了 | 链有 2 条以上仍失败;s:/i: 关系不连续 | 对端:叶子在前、中间在后 |
| 客户端信任库缺失 | 换任何客户端都失败;精简镜像 /etc/ssl/certs 为空 | 己方:更新 CA 包或显式指定路径 |
| 地址与证书域名不一致 | 报 hostname mismatch 而非 issuer 类错误 | 己方:改用证书覆盖域名或补 SAN |
判定依据: 报 unable to get local issuer 优先怀疑链不全;报 hostname 优先怀疑地址写错;报 expired 才是时间问题。信任库缺根时链完整仍失败,只能己方补根或对端换公共 CA。
三、诊断步骤
步骤一,看链有几层: openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts,输出中 s:/i: 应有 2 组以上且首尾相接;只有 1 组即只下发了叶子证书。
步骤二,看验证结论: openssl s_client -connect api.example.com:443 -verify_return_error,Verify return code: 0 为通过;20/21 为链断裂信号。
步骤三,用生产同款客户端复验: curl -vI https://api.example.com/health --cacert /etc/ssl/certs/ca-certificates.crt。命令行失败而浏览器正常,定性完成。
四、错误分流实现
关闭校验(verify=False)会让中间人、过期、域名不符全部静默。规范做法是按错误类型分流,让每种失败给出可执行下一步:
import requests
from requests.exceptions import SSLError
def fetch(url: str, ca_bundle: str = "/etc/ssl/certs/ca-certificates.crt"):
"""按错误类型分流,不关闭校验。"""
try:
with requests.get(url, timeout=(3.05, 10), verify=ca_bundle) as resp:
resp.raise_for_status()
return resp.json()
except SSLError as exc:
text = str(exc)
if "unable to get local issuer certificate" in text:
raise RuntimeError("证书链不完整,先查对端是否下发中间证书") from exc
if "certificate has expired" in text:
raise RuntimeError("证书已过期,联系对端续期并确认生效") from exc
if "hostname" in text.lower():
raise RuntimeError("证书域名与调用地址不一致,检查 SAN") from exc
if "self-signed" in text or "self signed" in text:
raise RuntimeError("对端为自签证书,需显式指定 CA 文件") from exc
raise
实现约束: 其一,verify 接受 CA 文件路径而非布尔值,自签场景传入独立 PEM 路径;其二,timeout 用元组分别设建连与读超时;其三,握手失败属确定性错误,不加重试,重试只用于连接重置类瞬时错误。
判定依据: 分流后每类错误对应唯一处置动作,且代码中不存在关闭校验的兜底路径。
五、续期生效清单
换证书后集中的第二类故障:证书正确、配置正确,生效链路有一段没跟上。
- 网关监听器绑定的是证书 ID,控制台更新后未必自动同步——比对实际下发指纹与控制台指纹;
- 加速层与反向代理证书独立配置,不同步表现为「部分地域正常、部分报错」;
- 长连接池持有旧 TLS 会话,进程重启前持续复用——换证书后重启长连接服务,本质是清空旧会话。
六、实施检查清单
- 链层数 ≥ 2 且
s:/i:首尾相接 Verify return code: 0- fullchain 顺序为叶子 → 中间,无空行
- 生产同款客户端实测通过,不以浏览器为准
- 精简镜像 CA 包非空
- 续期后比对实际下发指纹
- 换证书后重启长连接服务
- 无
verify=False兜底路径 - SSL 错误按四类分流告警
七、结论
证书问题的判断标准只有一条:用生产环境里真实的客户端去验。「浏览器打得开」在证书链问题上不构成证据,因为浏览器会替你补链,而你的业务客户端不会。
以上证书链校验与分流方案已在多个平台对接场景中验证,相关接口与数据服务方案如下。
相关参考: 跨境电商平台接口与数据服务 方案