SSL 证书链不完整:静默的 HTTPS 杀手

你的 SSL 证书是有效的,到期日还在几个月之后,openssl 也说签名没问题。但访客在 Chrome 中看到"您的连接不是私密连接",在 Firefox 中看到"警告:潜在的安全风险",在 Safari 中看到吓人的红色页面。到底怎么回事?

90% 的情况下答案是:你缺少中间证书

理解证书链

SSL 证书不是孤立存在的。它处于一条信任链中:

根 CA(浏览器内置信任)
  └── 中间 CA(由根 CA 签发)
       └── 你的服务器证书(由中间 CA 签发)

浏览器内置了数百个根证书。但它们没有中间证书。如果你的服务器只发送了叶子证书而没有附带中间证书,浏览器就无法构建一条完整的路径回到受信任的根 —— 于是直接拒绝连接。

修复方法很简单:让服务器在握手时把中间证书一起发给客户端。

Advertisement

步骤 1:使用 OpenSSL 诊断

连接你的服务器,检查它发回的证书链:

openssl s_client -connect example.com:443 -showcerts

按 Enter,等待输出。寻找证书块 —— 每个块以 -----BEGIN CERTIFICATE----- 开始,以 -----END CERTIFICATE----- 结束。配置正确的服务器应该至少发送两个证书:

Certificate chain
 0 s:/CN=example.com
   i:/C=US/O=Let's Encrypt/CN=R3        # <-- 这是中间证书
 1 s:/C=US/O=Let's Encrypt/CN=R3
   i:/O=ISRG Root X1                     # <-- 这是根证书(通常不发送)
---

如果只看到 0 s:/CN=example.com1 为空或不存在,说明服务器只发送了叶子证书 —— 链不完整。

步骤 2:检查缺失了什么

运行以下命令确认根本原因:

openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt example.com.crt

如果输出 error 20 at 0 depth lookup: unable to get local issuer certificate,说明服务器证书因缺少中间证书而无法验证。

你也可以查看服务器证书的签发者来确定需要哪个中间证书:

openssl x509 -in example.com.crt -noout -issuer

输出类似 issuer = /C=US/O=Let's Encrypt/CN=R3,告诉你需要的中间证书是哪个。

步骤 3:获取中间证书

从你的证书颁发机构(CA)下载中间证书。以下是常见来源:

将中间证书保存为文件,例如 intermediate.crt

步骤 4:合并为完整链文件

关键是顺序很重要。服务器证书在前,中间证书在后,根证书通常不需要包含。将它们拼接:

# 服务器证书在前,然后是中间证书
cat example.com.crt intermediate.crt > fullchain.crt

# 如果有多个中间证书(较少见):
cat example.com.crt intermediate1.crt intermediate2.crt > fullchain.crt

验证合并后的文件顺序正确:

grep "BEGIN CERTIFICATE" fullchain.crt | cat -n

应该看到每个证书按顺序编号:

     1  -----BEGIN CERTIFICATE-----   # 你的服务器证书
     2  -----BEGIN CERTIFICATE-----   # 中间证书

步骤 5:更新服务器配置

将 Web 服务器指向完整链文件,而不是仅指向服务器证书。

Nginx

# /etc/nginx/conf.d/example.com.conf 或类似路径
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/ssl/certs/fullchain.crt;   # <-- 完整链
    ssl_certificate_key /etc/ssl/private/example.com.key;

    # ... 其他配置
}

测试并重载:

nginx -t
nginx -s reload

Apache

# /etc/apache2/sites-available/example.com-le-ssl.conf
<VirtualHost *:443>
    ServerName example.com

    SSLEngine on
    SSLCertificateFile /etc/ssl/certs/fullchain.crt     # <-- 完整链
    SSLCertificateKeyFile /etc/ssl/private/example.com.key
    # 使用 fullchain.crt 时无需 SSLCertificateChainFile

    # ... 其他配置
</VirtualHost>

测试并重载:

apachectl configtest
systemctl reload apache2    # 或: systemctl reload httpd

步骤 6:使用在线工具验证

重载后再次运行 openssl 确认:

openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null | grep "s:/" | head -3

同时使用外部工具做完整验证:

SSL Labs 评级为 "A" 或更高,说明链完整、协议现代、没有信任问题。

常见 CA 及中间证书下载地址

证书颁发机构 中间证书下载地址 备注
Let's Encrypt letsencrypt.org/certificates 通常由 Certbot 自动处理;使用 R3/R10/R11
DigiCert digicert.com/kb/digicert-root-certificates.htm 有多个世代的中间证书;需匹配你证书的签发者
Sectigo (Comodo) sectigo.com/knowledge-base/ssl-certificates/intermediate-ca-certificates 使用 "Sectigo RSA Domain Validation" 中间证书
GlobalSign globalsign.com/en/repository/roots-and-intermediates 根据 OV 还是 DV 选择正确的中间证书
Amazon Trust Services aws.amazon.com/certificate-manager/resources/ ACM 证书由 ACM 自动处理
进阶提示:如果你使用 Let's Encrypt 的 Certbot,--fullchain 参数会自动生成合并文件。检查 /etc/letsencrypt/live/example.com/fullchain.pem —— 它已经包含了中间证书。

相关指南