🔒 第04章 · 证书查看与排错

openssl 查看/排查常见报错

04 | 证书查看与排错(openssl)

证书出问题时(浏览器报警、续期失败、访问报错),第一反应不是懵,而是用 openssl 把证书"读"出来、把链"对"起来、把报错"定位"出来。这一章把 openssl 排查的整套手法讲透,命令和报错都围绕本机真实证书 bangdream.xyz 展开。

openssl 是什么

openssl 是 TLS 的事实标准工具箱:生成密钥、签发证书、查看证书、模拟握手、校验签名。它本身不帮你签证书,但能让你看清、诊断、验证手里证书的每一个细节。

# 本机版本(openssl 3.0.13)
openssl version

每张证书的几个"身份证字段"

回顾第一章:证书由 subject(我是谁)、issuer(谁签的我)、subjectAltName(我能覆盖哪些域名)、有效期、公钥、签名组成。openssl 把它们一一照出来。

查看本地证书文件

/etc/letsencrypt/live/<域>/ 下有四类文件,对应不同用途:

文件 内容 用途 查看命令
fullchain.pem 叶证书 + 中间证书(整条链) Nginx 用这个 用 x509 或 crl2pkcs7
cert.pem 只有叶证书 排错时看本站信息 用 x509
chain.pem 只有中间证书 看是哪个中间 CA 签的 用 x509
privkey.pem 私钥 绝不能外泄 看私钥信息用 pkey
# 看叶证书的关键字段(本机实测输出)
openssl x509 -in /etc/letsencrypt/live/bangdream.xyz/cert.pem -noout \
  -subject -issuer -dates -ext subjectAltName

# 等价地看整条链(文件里多个证书,用 crl2pkcs7 + pkcs7 逐个打印)
openssl crl2pkcs7 -nocrl -certfile /etc/letsencrypt/live/bangdream.xyz/fullchain.pem \
  | openssl pkcs7 -print_certs -noout

第一条的输出里你能看到 subject=CN = bangdream.xyz、issuer=O = Let's Encrypt, CN = YE1、两行 Dates 等——这正是证书的"身份字段"。记住 issuer 是 Let's Encrypt 就说明不是自签名。

查看证书是否到期

# -checkend 数字(秒)会在"剩余不足 N 秒时要到期"时报错并返回非零
openssl x509 -in /etc/letsencrypt/live/bangdream.xyz/cert.pem \
  -noout -checkend 86400 -date && echo "证书还够 1 天" || echo "证书将到期/已到期"
参数 作用 返回值
-dates 打印有效期起止 文本
-checkend <秒> 判断剩余是否大于 N 秒 满足则 0,不满足非 0

校验证书与私钥是否匹配

最经典的问题:配置里 ssl_certificate 和 ssl_certificate_key 不是一对(常见于换证书时只换了一半)。校验方法是对比两者的公钥指纹。

# 提取证书里的公钥指纹
printf "" | openssl x509 -in /etc/letsencrypt/live/bangdream.xyz/cert.pem \
  -noout -pubkey | openssl sha256

# 提取私钥对应的公钥指纹
openssl pkey -in /etc/letsencrypt/live/bangdream.xyz/privkey.pem \
  -pubout -outform DER | openssl sha256

两张指纹一致 → 证书和私钥是匹配的一对;不一致 → 直接把私钥换错/证书发错。这是排查 Cannot find specified certificate key 的首选命令。

易错点:privkey.pem 是私钥,pkey 命令读的是私钥;cert.pem 里的 -pubkey 输出是 DER 编码的公钥。二者要转成同一种摘要方式再比。永远不要把 privkey.pem 贴进帖子或传给他人。

查看证书链是否完整(缺中间证书?)

用 crl2pkcs7 把 fullchain 的每一级都打印出来,看它是否"能追到根"。

openssl crl2pkcs7 -nocrl -certfile /etc/letsencrypt/live/bangdream.xyz/fullchain.pem \
  | openssl pkcs7 -print_certs -noout

本机输出能看到"雷神"四级:叶(bangdream.xyz)→ 中间(YE1)→ 根(ISRG Root)。如果只打印出 1 行(只有叶子)说明链里缺中间证书,这正是导致浏览器 unable to get local issuer certificate 的根因。

模拟一次真实握手(ssl_client)

要验证远程证书而不是本地文件,用 openssl s_client 主动向服务器发起握手,抓回证书。

# 连本机站点并打印证书
echo | openssl s_client -connect bangdream.xyz:443 -servername bangdream.xyz 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates -ext subjectAltName

# 只看验证结果(0 表示信任链验证通过)
echo | openssl s_client -connect bangdream.xyz:443 -servername bangdream.xyz 2>&1 \
  | grep "Verify return code"

-servername 是 SNI。访问多域名服务器时必须带上,否则默认取的是服务器"第一个"证书,会让你误判。

常见报错排查表

浏览器/openssl 报错 含义 排查动作
unable to get local issuer certificate 链不完整,缺中间证书 换用 fullchain.pem,看 crl2pkcs7 是否打印多级
certificate expired / not yet valid 过期/未生效 -checkend,看 -dates,必要时 certbot renew
浏览器红锁 + 域名不匹配 SAN 不含该域名 -ext subjectAltName,确认 SAN 里有没有这个域名
Cannot find specified certificate key 证书/私钥不配对 比对两者公钥指纹(见上)
no peer certificate available 443 没启用 SSL / 端口错 ss -ltnp | grep 443,看 nginx 是否 listen 443 ssl
permission denied (publickey) 类 证书文件权限错误 ls -la live/<域>/,确认可被 nginx 用户读取
浏览器 TLS 报"使用的加密协议" 协议太旧被拒 看 nginx ssl_protocols 是否只开了 TLS1.2/1.3

权限问题:为什么 nginx 读不到证书

/etc/letsencrypt/live/<域>/ 目录默认是 root 且 drwx------(0700)。Nginx worker 常以 www-data/nginx 用户运行,需要能最终读取到证书文件。

ls -la /etc/letsencrypt/live/bangdream.xyz/
# drwx------ root root  -> 这个目录默认只有 root 能进

易错点:如果 nginx 报权限相关错误,常见原因是 live/ 目录权限过严。通常 certbot 会正确设置,但手动改动过目录结构、或迁移证书后,需要检查 nginx -t 是否报权限,必要时 chmod / 调整目录可读,或用 sudo -u www-data 测试读取。

场景:三步定位"为什么访问 https 报错"

以本机站点为例,一次完整的排查流程:

# 1. Nginx 配置没错
nginx -t

# 2. 证书还没到期、链完整
openssl x509 -in /etc/letsencrypt/live/bangdream.xyz/cert.pem -noout -dates
openssl crl2pkcs7 -nocrl -certfile /etc/letsencrypt/live/bangdream.xyz/fullchain.pem \
  | openssl pkcs7 -print_certs -noout

# 3. 握手能通、验证通过
echo | openssl s_client -connect bangdream.xyz:443 -servername bangdream.xyz 2>&1 \
  | grep "Verify return code"

如果 1-3 全过,基本可以断定证书本身没问题,问题在业务层(如 X-Forwarded-Proto、后端、DNS)。

延伸:用 openssl 生成自签名测试证书

开发环境临时用,别用在生产:

openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout /tmp/dev.key -out /tmp/dev.crt -days 30 \
  -subj "/CN=dev.example.com"

安全提醒:自签名证书"谁的根都不是",浏览器必然报警。生产环境一律用 Let's Encrypt(certbot),别把自签名证书挂上去。


下一章:05-security-headers → OCSP / HSTS / 安全响应头,给已经跑通的 HTTPS 加最后一层加固。