HTTPS 证书问题排查:从「不安全」警告到证书链完整

配好 HTTPS 只是开始。浏览器地址栏出现红色警告时,别慌,按现象分五类,每类都有固定的排查路径。

一、先做通用体检

bash
# 1. 看证书有效期与颁发者
echo | openssl s_client -connect www.bzii.cn:443 -servername www.bzii.cn 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

# 2. 看完整证书链(关键:chain 里有没有中间证书)
openssl s_client -connect www.bzii.cn:443 -servername www.bzii.cn -showcerts </dev/null 2>/dev/null

# 3. 验证结果
openssl s_client -connect www.bzii.cn:443 -servername www.bzii.cn </dev/null 2>/dev/null | grep -E "Verify|depth"

# 4. HTTP 状态码
curl -o /dev/null -s -w "%{http_code}\n" https://www.bzii.cn

二、现象一:证书链不完整(部分设备报不安全)

表现:电脑浏览器正常,手机或某些安卓设备提示证书不受信任。

原因:Nginx 只配了服务器证书,没配中间证书(intermediate)。

bash
# 错误写法(只有站点证书)
ssl_certificate     /etc/letsencrypt/live/www.bzii.cn/cert.pem;

# 正确写法(fullchain = 站点证书 + 中间证书)
ssl_certificate     /etc/letsencrypt/live/www.bzii.cn/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.bzii.cn/privkey.pem;

certbot 生成的目录里四个文件的区别:

文件内容
cert.pem只有站点证书
chain.pem只有中间证书
fullchain.pem站点 + 中间(用这个
privkey.pem私钥

三、现象二:域名不匹配(NET::ERR_CERT_COMMON_NAME_INVALID)

原因:证书里没有你访问的那个域名。比如证书只签了 www.bzii.cn,你访问 www.bzii.cn

bash
# 看证书覆盖了哪些域名(SAN 列表)
echo | openssl s_client -connect www.bzii.cn:443 -servername www.bzii.cn 2>/dev/null \
  | openssl x509 -noout -text | grep -A1 "Subject Alternative Name"

解法:签发时把所有要用的域名都带上,或直接用通配符:

bash
# 多域名
sudo certbot --nginx -d www.bzii.cn -d www.bzii.cn -d api.bzii.cn

# 通配符(必须用 DNS-01 校验)
sudo certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials ~/.cloudflare.ini \
  -d "*.www.bzii.cn" -d www.bzii.cn

四、现象三:混合内容(Mixed Content)

表现:地址栏锁是灰的或带感叹号,控制台报 Mixed Content: loaded over HTTP

原因:HTTPS 页面里引用了 http:// 的图片、脚本、样式。

bash
# 找出页面里的 http 资源
curl -s https://www.bzii.cn | grep -o "http://[^"]*" | sort -u

解法优先级:

  1. 改成相对协议或全站 https:src="//cdn.example.com/a.js" 或直接 https://
  2. 图片等静态资源搬到自己的域名下;
  3. upgrade-insecure-requests 兜底(强制把 http 请求升级为 https):
nginx
add_header Content-Security-Policy "upgrade-insecure-requests";

五、现象四:证书过期

bash
# 手动续期
sudo certbot renew
sudo systemctl reload nginx

# 看为什么自动续期没成功
systemctl status certbot.timer
journalctl -u certbot.service
sudo certbot renew --dry-run

自动续期失败的三个常见原因:

  1. 80 端口被挡:HTTP-01 校验需要 80 端口可达,防火墙/安全组要放行;
  2. CDN 挡住:Cloudflare 橙色云会拦截校验路径,临时改灰或改用 DNS-01;
  3. Nginx 没重载:证书更新了但 Nginx 还加载旧文件,加个 deploy hook:
bash
echo "reload hook" > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
cat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh <<EOF
#!/bin/sh
systemctl reload nginx
EOF
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

六、现象五:HSTS 导致的「删不掉」的报错

表现:证书修好了,浏览器还是强制 HTTPS 并报错。

原因:之前访问时服务器下发过 HSTS 头,浏览器缓存了(最长一年)。

解法:

  • Chrome 访问 chrome://net-internals/#hsts,在 Delete domain 里输入域名删除;
  • 或者等 max-age 过期(如果你设了 31536000,那就是一年)。

这也说明 HSTS 别乱加:确认 HTTPS 全站稳定了再加,建议先用小 max-age(max-age=300)测试。

七、一份排查顺序

  1. curl 看状态码 → 确认是证书问题还是服务问题;
  2. openssl 看有效期和 SAN → 排除过期/域名不匹配;
  3. -showcerts 看链 → 排除中间证书缺失;
  4. 页面源码搜 http:// → 排除混合内容;
  5. 还不行 → SSL Labs 完整评测,它会明确指出问题。