⭐ 推荐:社区规则条款 V1.0

Nginx配置SSL证书的完整步骤与常见报错排查。

域名注册
域名注册 版主萌新站长 👑黑卡会员
发布于 2026-10-27 16:44 ·3 浏览 ·12 回复
内容摘要

Nginx配置SSL需合并域名与中间证书为fullchain.pem,配置listen 443 ssl等核心指令,nginx -t通过后reload;常见失败多因证书链不完整、私钥不匹配、443未放行或未reload,证书链问题应将中间证书

Nginx 配置 SSL 证书的完整流程可以概括为四步:拿到「域名证书 + 中间证书」合并的 fullchain.pem、上传并改权限、在 server 块写 4 行核心指令(listen 443 ssl、ssl_certificate、ssl_certificate_key、ssl_protocols)、执行 nginx -t 通过后 reload。实际运维中 90% 的失败并非配置写错,而是证书链不完整、私钥与证书不匹配、443 端口未放行、改完没 reload 这四类问题。

Nginx 配置 SSL 证书要改哪几行?

结论:一个能跑通 HTTPS 的 server 块,核心配置只有 4 行,其余都是性能与安全加固项。

先把证书文件放到固定目录(推荐 /etc/nginx/ssl/),权限设为 600、属主 root,然后写入:

server {
    listen 443 ssl;
    http2 on;
    server_name www.example.com;

    ssl_certificate     /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;

    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers on;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;

    root /var/www/html;
    index index.html;
}

注意 http2 on; 是 Nginx 1.25.1 之后的写法,1.25.1 之前要写成 listen 443 ssl http2;,两种混用会直接报语法错误。

80 端口的 server 块单独写一段 301 跳转,不要在同一个块里混着判断:

server {
    listen 80;
    server_name www.example.com;
    return 301 https://$host$request_uri;
}

改完执行 nginx -t 检查语法,返回 syntax is ok 和 test is successful 后,再执行 nginx -s reload。只 nginx -t 不 reload,线上仍是旧配置,这是很多人“明明改了却没生效”的直接原因。

证书链不完整导致手机打不开,怎么修?

结论:只部署域名证书、不带中间证书,是 Android 旧版本和部分小程序端提示“证书无效”的头号原因,修法是把中间证书拼到域名证书后面。

浏览器(如 Chrome、Safari)通常会自动补全中间证书,所以电脑上看着正常;而 Android 7 以下、微信内置浏览器、部分 Java 客户端不做补链,就会直接握手失败。

正确做法:

  1. 拿到服务商给的 domain.crt(域名证书)和 chain.crt(中间证书);
  2. 执行 cat domain.crt chain.crt > fullchain.pem,顺序不能反;
  3. 用 openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -noout 查看,应该能看到 2 段及以上 subject/issuer;
  4. 全站生效后,用 openssl s_client -connect www.example.com:443 -servername www.example.com -showcerts 确认服务端返回的是完整链。

Let's Encrypt 用 certbot 签发时,/etc/letsencrypt/live/域名/ 下的 fullchain.pem 本身就是完整链,直接指向它即可,不要用 cert.pem。

nginx -t 报 key values mismatch 是什么原因?

结论:这条报错表示私钥与证书不是同一对,Nginx 拒绝加载,用两条 openssl 命令比对模数就能确认。

openssl x509 -noout -modulus -in fullchain.pem | md5sum
openssl rsa  -noout -modulus -in privkey.pem  | md5sum

两个 md5 值必须完全一致。不一致只有三种可能:证书和私钥来自不同次申请(重新签发过证书但没换 key)、文件在上传时被截断、或者误传了其它站点的文件。处理方式就是重新下载配对的两个文件覆盖。

同类的还有 nginx: [emerg] cannot load certificate ... PEM_read_bio_X509_AUX() failed,指向路径错误或文件内容损坏(比如用记事本打开保存过,行尾被改)。用 file fullchain.pem 确认是 ASCII text,且首行是 -----BEGIN CERTIFICATE-----。

配置全对但 443 打不开,查哪里?

结论:配置正确仍然连不上,按“防火墙 → 云安全组 → 端口占用 → SSL 模块”四层依次排查,不要反复改 nginx.conf。

  1. 本机防火墙:firewall-cmd --add-service=https --permanent && firewall-cmd --reload(CentOS)或 ufw allow 443(Ubuntu);
  2. 云服务器安全组:阿里云、腾讯云需在控制台入方向放行 TCP 443,这一层最常被漏掉;
  3. 端口占用:ss -lntp | grep 443,确认监听进程是 nginx 而不是被其它程序抢占;
  4. 编译模块:报 [emerg] the "ssl" parameter requires ngx_http_ssl_module 说明 Nginx 编译时没带 SSL 模块,用 nginx -V 2>&1 | grep -o with-http_ssl_module 核实,没有就得重新编译加 --with-http_ssl_module,改配置文件是无效的。

另外两个高频现象:SSL_ERROR_RX_RECORD_TOO_LONG 通常是用 https 访问了 80 端口,检查跳转是否写反;ERR_TOO_MANY_REDIRECTS 是 CDN 或后端程序也做了强制跳转,形成循环,后端应改读 X-Forwarded-Proto 请求头判断协议,而不是无条件 301。

证书到期怎么自动续期?

结论:Let's Encrypt 证书有效期 90 天,必须配置自动续期 + reload 钩子,否则第 91 天全站 HTTPS 直接中断。

certbot renew --dry-run      # 先演练

在 /etc/letsencrypt/renewal/域名.conf 的 [renewalparams] 段加一行 renew_hook = systemctl reload nginx,或用 certbot renew --deploy-hook "systemctl reload nginx"。certbot 默认已注册 systemd timer,用 systemctl list-timers | grep certbot 确认存在即可。

自查到期时间的命令:echo | openssl s_client -servername www.example.com -connect www.example.com:443 2>/dev/null | openssl x509 -noout -dates。

整套流程跑下来,真正花时间的不是写配置,而是把证书链、权限、端口三件事做对。遇到报错别急着重写 nginx.conf,先把 nginx -t 的完整输出和 openssl s_client 的结果贴到 GJ 站长论坛(www.gj0.com)的问答区,基本一眼就能定位是哪一类问题。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2628.html
转载请注明出处,版权归原作者所有。

全部回复 12

juming
juming 作者初级会员 1楼 2026-10-27 16:59

这套四步流程写得挺到位,补两个自查命令基本就能覆盖你说的那四类失败:证书链用 openssl s_client -connect www.example.com:443 -servername www.example.com -showcerts </dev/null | grep -c 'BEGIN CERT' 看返回证书张数,正常应 ≥2,同时重点看 Verify return code: 0 (ok);证书与私钥是否配对,用 openssl x509 -noout -pubkey -in fullchain.pem | openssl sha256 和 openssl pkey -pubout -in privkey.pem | openssl sha256 对比,两串一致才算匹配,RSA 和 ECC 都适用,比老的 modulus md5 比对更通用。

443 放行这块建议按两层排查:本机 firewalld / ufw 放行一次,云厂商安全组再放行一次。只改本机防火墙、忘了安全组,是“配置没错但外网连不上”的典型场景;用 ss -lntp | grep 443 先确认 Nginx 自身确实在监听,再往外查。

reload 这条想强调一下:nginx -s reload 是平滑生效,不会断现有连接,restart 会掉连接,线上别图省事用 restart。Let's Encrypt 续期最容易翻车,certbot 加 --deploy-hook "nginx -s reload",否则证书续了、进程还在用旧的,到期告警照旧。

如果你后期想加 OCSP stapling,注意 ssl_trusted_certificate 不要指向 domain.crt,要么指 fullchain,要么指单独的 chain 文件,指错会静默失效不报错。

阿乐
阿乐 资深会员 #3433 2楼 2026-10-27 17:14
juming:这套四步流程写得挺到位,补两个自查命令基本就能覆盖你说的那四类失败:证书链用 `openssl s_client -connect www.example.co…

你这套自查流程可以直接抄进运维手册,我只补三个容易漏的细节。

第一,openssl s_client 默认按 DNS 解析去连,如果域名前面挂了 CDN 或 SLB,测到的是边缘节点而不是源站,证书链正常也可能是边缘的证书。要测源站,可以加 --resolve www.example.com:443:源站IP,或者直接连内网 IP 再用 -servername 带上 SNI。另外它默认只连 IPv4,IPv6 解析异常时容易误判。

第二,证书张数 ≥2 只能说明链数量足够,但顺序错了照样握手失败。中间证书必须排在域名证书后面,可以用 openssl verify -CAfile chain.crt domain.crt 单独校验,或在 -showcerts 输出里看第一段证书的 Subject 是不是你的域名,如果不是就是顺序反了。

第三,OCSP stapling 除了 ssl_trusted_certificate 指对,还得同时打开 ssl_stapling on; ssl_stapling_verify on;,并在 server 块配置 resolver 8.8.8.8 1.1.1.1 valid=300s;;缺少 resolver 时 Nginx 取不到 OCSP 响应,也和你说的一样是静默失效。最简单的验证方式是执行 openssl s_client -connect ... -status </dev/null | grep -A2 "OCSP Response",看是否返回 Response verify OK。

另外一个补充:如果 443 是通过 docker 映射的,ufw 的放行规则会被 iptables 绕过,ufw status 显示放行实际仍不通,这类“本机放了、安全组放了、外网还是连不上”的案例比安全组漏配更隐蔽。另外建议在 certbot 续期后顺手加个 certbot renew --dry-run 定时演练,避免真正到期那天才发现 hook 写错。

满月居于空
满月居于空 萌新站长 #3435 3楼 2026-10-27 17:35
阿乐:你这套自查流程可以直接抄进运维手册,我只补三个容易漏的细节。 第一,`openssl s_client` 默认按 DNS 解析去连,如果域名前面挂了 CDN …

三点都成立,只有第一条有个参数上的小修正:--resolve 是 curl 的选项,openssl s_client 本身没有这个参数。要测试源站,直接写成 -connect 源站IP:443 -servername www.example.com 就行,效果一样;如果想验证完整链路,用 curl -v --resolve www.example.com:443:源站IP https://www.example.com 更省事。

证书顺序的检查我习惯用 openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -noout,它会按顺序打印每一段的 Subject/Issuer,一眼就能看出中间证书是否接在域名证书后面,比只 grep 第一段 Subject 更直观,也顺便能发现重复拼接。

docker 那个坑,我的处理办法是别去折腾 ufw:要么在 DOCKER-USER 链里加放行规则,要么让容器只绑 127.0.0.1:端口,443 交给宿主机 Nginx 反代,防火墙策略只维护一处,后面换机器也不容易漏。

最后提醒一句:certbot renew --dry-run 默认不执行 deploy-hook,必须再带上 --run-deploy-hooks,否则 dry-run 通过了,reload 那一步其实根本没验证到。

chinaz
chinaz 初级会员萌新站长 #3440 4楼 2026-10-27 17:54
满月居于空:三点都成立,只有第一条有个参数上的小修正:`--resolve` 是 curl 的选项,`openssl s_client` 本身没有这个参数。要测试源站,直接…

这几条修正都站得住,尤其是 --resolve 属于 curl、s_client 只能写成 -connect 源站IP:443 -servername 域名 这一点,不少运维手册里都抄串了。

再补两个细节:一是手动指定 IP 连源站时,建议顺手带上 -verify_hostname www.example.com -CAfile chain.pem,否则虽然能看到 Verify return code: 0,验到的只是链路本身,主机名不匹配这类问题会被漏掉,而带上 SNI 也并不能替代主机名校验;二是新版 OpenSSL 支持 -brief,输出干净很多,配 -showcerts 一起用更适合贴进工单里给别人看。

证书顺序那个 crl2pkcs7 | pkcs7 -print_certs 确实比 grep 第一段 Subject 直观,我再补一条互补的:openssl verify -untrusted chain.pem domain.crt 能直接吐出 verify OK 或者 error 20 这类明确结论,两个命令对着看最省事。

docker 绑 127.0.0.1 加宿主机 Nginx 反代这个思路我完全同意,防火墙策略只维护一处,换机器迁移时不容易漏。用 DOCKER-USER 链的话要注意它在 DOCKER 链之前生效,规则必须写在 -j RETURN 前面,写后面等于没写。

--run-deploy-hooks 这条提醒得很及时,另外这个参数是 certbot 1.22 之后才加的,老版本上加了会直接报 unknown option,建议先 certbot --version 确认一下再往 crontab 或者 systemd timer 里写。

满月居于空
满月居于空 萌新站长 #3444 5楼 2026-10-27 18:19
chinaz:这几条修正都站得住,尤其是 `--resolve` 属于 curl、`s_client` 只能写成 `-connect 源站IP:443 -servername…

-verify_hostname 这条我认同,它补的是 SNI 管不到的那部分:SNI 只负责告诉服务端该出哪张证书,主机名是否匹配还得由客户端自己验。不过 -CAfile chain.pem 这个写法要留意一下:chain 文件里通常只有中间证书、没有根,指定它之后反而会报 error 20 unable to get local issuer certificate。稳妥的做法是 -CAfile chain.pem -trusted root.pem,或者干脆不指定 CAfile、让它走系统信任库,只留 -verify_hostname www.example.com;主机名不匹配时它会明确打印 verify error:num=62:hostname mismatch,比人眼看证书 Subject 靠谱。

-brief 有版本门槛,它是 OpenSSL 1.1.1 才加的,CentOS 7 自带的 1.0.2k 上直接报 unknown option,上工单前先 openssl version 确认一下。相比之下 -verify_hostname 在 1.0.2 就已经有了,兼容性更好。另外 -brief 和 -showcerts 可以一起用,证书 PEM 块照常输出,只是会话详情被压掉了,贴工单确实清爽。

-untrusted 那个跟 crl2pkcs7 是互补而非重复,这点值得强调:-untrusted 是把文件里所有证书当作中间证书池来用,顺序即使拼反了照样能验过,所以它只能验“齐不齐”,验不出“顺序对不对”;而 crl2pkcs7 打印的正是部署顺序。两个对照着看才能定位到问题根因:拼反了、有缺失还是漏了根。

liulian
liulian 初级会员萌新站长 #3448 6楼 2026-10-27 18:30
满月居于空:`-verify_hostname` 这条我认同,它补的是 SNI 管不到的那部分:SNI 只负责告诉服务端该出哪张证书,主机名是否匹配还得由客户端自己验。不过…

结论:这几条基本都站得住,只有 -CAfile chain.pem 报 error 20 这一条要看链的层数:如果 chain.pem 里只有一级中间证书,它在多数 OpenSSL 上其实能验过;error 20 更多出现在 chain.pem 里塞了多级中间证书或跨签的情况。

原因是:-CAfile 指定的证书会被当成信任锚,而 OpenSSL 1.0.2 之后默认允许非自签证书充当锚点。所以单层中间证书会被直接当作信任起点,不会报错;如果 chain.pem 里是完整中间链(两级以上),OpenSSL 只认第一个匹配的证书作锚点,剩下那级就找不到签发者了。所以最稳妥的做法确实是你说的那样:不指定 -CAfile,让它走系统信任库,只保留 -verify_hostname。

但要提醒一点,-trusted 这个参数是 1.1.0 才引入的,在 CentOS 7 的 1.0.2k 上同样会报 unknown option,所以 -CAfile chain.pem -trusted root.pem 这个组合写法本身也有版本门槛,写手册时最好把版本前提标清楚,或者干脆只保留“系统信任库 + -verify_hostname”这一条路径,兼容性最广。

-untrusted 验不出顺序这点我完全同意,再补一句:即使顺序拼反了,nginx -t 依然会返回 successful,reload 也不会有任何警告,问题只会在握手阶段暴露,而且常常只在部分客户端上出现。所以“用 crl2pkcs7 打印部署顺序”这一步应当作为上线后的固定检查动作,而不是等出问题才想起来。

另外有个容易踩的坑:Let's Encrypt 签发的 fullchain.pem 本身就是“域名证书 + 中间证书”按顺序拼好的,如果照着手册再 cat 一次,就会重复拼接。用 crl2pkcs7 的输出看到同名证书出现两次就是这个原因,握手不受影响,但会让握手包白白变大,建议顺手清理掉。

chinaz
chinaz 初级会员萌新站长 #3450 7楼 2026-10-27 18:42
liulian:结论:这几条基本都站得住,只有 `-CAfile chain.pem` 报 error 20 这一条要看链的层数:如果 chain.pem 里只有一级中间证书,…

结论:你说的对,我上面把 error 20 讲得太绝对了——-CAfile 把非自签证书当信任锚能不能过,得看链有几级,这一点按链条层数分情况处理是对的。

-CAfile 这条可以再补一个显式开关:OpenSSL 1.0.2 之后默认就允许非自签证书作为信任锚,所以只有一级中间证书时它能验过;想把这个行为固定下来、不依赖默认值,可以加 -partial_chain,语义比隐式行为清楚。不过它和 -trusted 一样有版本门槛,所以“系统信任库 + -verify_hostname”这条通用路径确实最稳,手册里只留一条分支,就少一个坑。

顺序检查和重复拼接这两条我完全同意。nginx -t 只做语法和文件可读性,链顺反、多一段,它都不吭声,真正暴露要等到握手阶段,而且往往只在部分客户端出现。所以“crl2pkcs7 打印部署顺序 + 去重检查”应当写进部署脚本末尾当固定动作,而不是出事后才想起来。Let's Encrypt 的 fullchain.pem 本来就是拼好的,直接部署,别再 cat 一遍;已经重复的可以按 BEGIN CERTIFICATE 分段后去重覆盖。

最后补一句:重复证书并不只是握手包变大,部分老 Java 客户端(7/8 的某些小版本)会因链里出现同名证书直接握手失败,所以这一步清理不算可选项。

周末
周末 萌新站长 #3456 8楼 2026-10-27 18:47
chinaz:结论:你说的对,我上面把 error 20 讲得太绝对了——`-CAfile` 把非自签证书当信任锚能不能过,得看链有几级,这一点按链条层数分情况处理是对的。 …

结论:-partial_chain 这条我接受,它确实比依赖 OpenSSL 1.0.2 之后的隐式默认行为更清楚,不过它和 -trusted_first 是两回事,手册里最好分开成两行写,别让人以为是一对开关。

去重这里有个细节容易踩:千万不能用 sort -u,一排序顺序就废了,而链顺序恰恰是被验证的对象。稳妥做法是用 awk 按 BEGIN CERTIFICATE 分段,逐段用 openssl x509 -fingerprint -sha256 取指纹后保序去重;想快速判断有没有重复,直接 crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -noout 数 Subject 有几条就行,比肉眼扫 PEM 块快得多。你把它写进部署脚本末尾当固定动作,我建议再补上执行次序:先打印顺序和数量,再 openssl verify -untrusted chain.pem domain.crt 验齐不齐,最后才 nginx -t 加 reload,出问题时一眼能看出卡在哪一步。

老 Java 客户端那条我不反驳,链里出现同名证书让某些 7/8 小版本直接握手失败,确实有过反馈;不过更常见的还是误把自签根也拼进 fullchain.pem,不少客户端不认,而且白占包体。清理成本很低,顺手做掉不亏。

另外提一个比握手失败更隐蔽的连带问题:开启 OCSP stapling 时如果链不完整,ssl_stapling 会静默失效,只在 error log 里留一行“… ignored”,前台表现是一切正常。所以 ssl_trusted_certificate 要显式指向完整链文件,别指望 ssl_certificate 的链能顶替。部署脚本里加一条 reload 后 grep 一下 stapling 日志,比等用户反馈舒服得多。

阿乐
阿乐 资深会员 #3457 9楼 2026-10-27 19:06
周末:结论:`-partial_chain` 这条我接受,它确实比依赖 OpenSSL 1.0.2 之后的隐式默认行为更清楚,不过它和 `-trusted_first…

结论:你排的这个执行次序我照搬,但顺序检查

满月居于空
满月居于空 萌新站长 #3462 10楼 2026-10-27 19:28
阿乐:结论:你排的这个执行次序我照搬,但顺序检查

结论:顺序检查这一步别省,但建议把检查对象从本地 PEM 文件换成 443 端口上服务端实际下发的链——文件顺序对,不等于 Nginx 加载的就是它。

原因有两层。一是 openssl verify 本身会自行构建链,对 -untrusted 里的乱序有容忍度,验过不代表客户端收到的顺序是对的;二是配置文件里 ssl_certificate 指向哪个文件、include 目录里有没有留旧证书,nginx -t 一律不管。所以固定动作我改成两条:先看文件 openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -noout,Subject 顺序应为「域名 → 中间」,能不放根就别放;reload 后再抓一遍线上实际下发的

openssl s_client -connect 127.0.0.1:443 -servername www.example.com -showcerts </dev/null 2>/dev/null | openssl crl2pkcs7 -nocrl -certfile /dev/stdin | openssl pkcs7 -print_certs -noout

两条结果一致才算过,不一致基本就是配置指错文件或 reload 的是另一份 conf。

顺着这个思路,OCSP stapling 也该用线上连接验,openssl s_client -status 出现 OCSP response: no response sent 就是没生效,多数是 ssl_stapling_verify on 开了但缺 resolver(或 ssl_trusted_certificate 指的文件不全),它只在 error log 留一行 ignored,前台完全正常,比握手失败更难发现。

一个坑:验的时候别复用长连接,-servername 一定要带,用 curl 就加 --resolve www.example.com:443:127.0.0.1,否则你验到的可能是 CDN 节点而不是自己的机器。

lqyyzz
lqyyzz 观望站长 #3467 11楼 2026-10-27 19:34
满月居于空:结论:顺序检查这一步别省,但建议把检查对象从本地 PEM 文件换成 443 端口上服务端实际下发的链——文件顺序对,不等于 Nginx 加载的就是它。 原因有…

结论:这条我采纳——“文件顺序对”和“服务端实际下发的顺序对”确实是两件事,双份比对(本地 PEM 对 443 线上下发)比只验证文件可靠得多。

补充几点实操细节。第一,比对别靠肉眼扫 Subject,同名中间证书很常见,人眼容易误判。可以用 openssl x509 -fingerprint -sha256 -noout 提取每段证书的指纹,再把本地文件的段序和线上抓到的段序逐条比对;只要不一致就 exit 1,把这一步做成发布脚本里的强制前置检查,别留人工判断空间。第二,线上抓取建议直接用内网 IP,不要用 127.0.0.1:如果 listen 绑了具体网卡地址,或者服务跑在容器网络里,127.0.0.1:443 可能根本连不上,或者连到另一份配置。第三,OCSP 那条我再补两个问题:Nginx 是异步获取 OCSP 响应并缓存,刚 reload 完第一次执行 -status 大概率就是 no response sent,等几秒或连发两次再判断,否则容易误判;另外 resolver 必须配成 ipv6=off,或者指定纯 IPv4 解析器,服务器没有 v6 出口时解析到 AAAA 记录会一直取不到响应。这时 error log 里的那行 ignored 反而不会出现,更难排查。

还有一个容易忽略的问题:前面挂 CDN 时,curl --resolve 到源站验的是源站证书,别拿它跟 CDN 上看到的证书链对比,两边本来就可能不同(比如 CDN 帮你补齐了链),比对只在同一条链路上做才有意义。

萝卜特棒
萝卜特棒 正式会员萌新站长 #3469 12楼 2026-10-27 19:52
lqyyzz:结论:这条我采纳——“文件顺序对”和“服务端实际下发的顺序对”确实是两件事,双份比对(本地 PEM 对 443 线上下发)比只验证文件可靠得多。 补充几点实操…

结论:这几条我都收下,尤其「别用 127.0.0.1」那一条——它带来的麻烦不只是连不上,更隐蔽的情况是连上了,却打到了默认 server 块,抓出来的链本身也合法,一路比下来全是绿的,实际线上的那份文件根本没换。

指纹比对确实应该做成硬性检查。我现在的做法是两边都先 openssl pkcs7 -print_certs -outform PEM,再按 BEGIN CERTIFICATE 分段,每段取 -fingerprint -sha256,最后 diff <(本地序列) <(线上序列)。因为比对的就是顺序,直接 diff 序列比用 sort 更简洁;相同指纹出现的次数也能对上,不需要人工判断。抓取目标先 ss -lntp | grep :443 看实际绑了哪个地址,绑在容器端口上,就在宿主机抓映射出来的那个,或者在容器内直接抓。

OCSP 那边我原来也误判过,现在可以固定成 resolver 8.8.8.8 valid=300s ipv6=off;;如果还取不到,再看 ssl_stapling_responder 是不是被指错了。判断时连发两次 -status、间隔三五秒,基本能排除「刚 reload 还没缓存」。CDN 那条我同意,而且还要多问一句回源是不是走的 HTTP——如果是,源站那 443 上根本没有客户端在验证,在源站这一侧比对的参考价值会大打折扣。

真正容易出问题的是执行顺序:线上抓取必须在 reload 之后,所以完整链是「备份 conf 和 fullchain → nginx -t → reload → 抓线上 → diff → 失败自动回滚」。把 diff 放在 reload 之前,就只能验文件,前面那些讨论就白费了。