上一篇 下一篇 分享链接 返回 返回顶部

SSL证书自动续期后Nginx未生效怎么办?香港服务器多域名HTTPS排查方法

发布人:Minchunlin 发布时间:2026-10-05 13:13 阅读量:8

浏览器仍显示旧的证书到期时间,但证书客户端日志已经提示“续期成功”,通常不代表续期失败。更常见的情况是:新证书已经写入磁盘,Nginx 进程仍在使用内存中的旧证书;或者 Nginx 配置引用了另一份旧文件,多域名请求又被其他 server 配置接管。

排查时不要一开始就重启服务或重复申请证书。建议按照“外部实际证书 → 本地证书文件 → Nginx 生效配置 → reload 日志 → 多域名 SNI 匹配 → 自动续期钩子”的顺序处理。这个顺序从低风险检查开始,可以较快区分是证书没有更新、Nginx 没有重新加载,还是访问请求根本没有到达预期的香港服务器。

先确认故障属于哪一层配图

先确认故障属于哪一层

HTTPS 证书问题至少涉及三层:

  1. ACME 客户端是否成功签发并保存新证书。
  2. Nginx 是否读取了正确路径,并在续期后重新加载。
  3. 客户端访问的域名是否通过 SNI 命中了正确的 server 配置。

可以先用下面的现象表缩小范围。

现象优先怀疑位置判断依据
本地证书日期已更新,公网仍显示旧证书Nginx 未 reload、访问了其他 IP 或 IPv6 配置磁盘文件和外部握手结果不一致
所有域名都显示旧证书reload 钩子失败、Nginx 没有重新加载多个站点同时未更新
只有一个域名显示旧证书该域名的证书路径、server_name 或 SNI 匹配异常其他域名可以正常显示新证书
nginx -t 失败配置语法、证书路径或权限问题失败时不应执行 reload
证书日期正确,但浏览器提示链不完整使用了叶子证书而不是 fullchain.pem证书本身新旧不是主要问题
指纹与本地新证书不同,且 DNS 指向多个地址当前请求没有到达正在续期的服务器需要分别核对 A、AAAA 和其他入口

Nginx 的优雅 reload 通常不会主动中断已有连接。新建立的 TLS 连接会读取新证书,已经建立的连接仍可能沿用原有会话,这是正常现象。因此,验证时要创建新的 HTTPS 连接,不能只观察已经打开的浏览器页面。

准备检查环境和配置备份

以下命令以使用 systemd 管理 Nginx 的 Linux 服务器为例,常见于 Debian、Ubuntu 等发行版。实际操作前先确认命令路径和服务名称,不要直接假设系统一定使用默认目录。

cat /etc/os-release
nginx -v
command -v nginx
systemctl status nginx --no-pager

如果 Nginx 是自定义编译的,可以查看配置文件路径:

nginx -V 2>&1 | grep -- '--conf-path'

在修改 Nginx 配置前,建议只备份配置目录,不要把私钥复制到公共目录或通过工单、聊天工具传递。

sudo cp -a /etc/nginx "/etc/nginx.backup-$(date +%F-%H%M%S)"
sudo nginx -T > "/tmp/nginx-before-$(date +%F-%H%M%S).conf" 2>"/tmp/nginx-before-error.log"

备份的作用是便于恢复被修改的配置。恢复时不要直接覆盖整个 /etc/nginx,因为备份之后可能又产生了其他正常变更。应先定位具体异常文件,恢复单个文件,然后执行 nginx -t,确认通过后再 reload。

同时确认 443 端口由哪个进程监听:

sudo ss -lntp | grep ':443'

如果没有输出,说明当前服务器并没有监听标准 HTTPS 端口;如果显示的不是预期 Nginx 进程,需要先确认 HTTPS 是否由其他服务处理。此时继续修改 Nginx 证书文件不会改变公网结果。

第一步:直接查看公网实际返回的证书

不要先相信浏览器页面上的证书信息,先用 OpenSSL 建立一次新的 TLS 连接。-servername 参数必须保留,它用于发送 SNI,模拟浏览器访问具体域名的行为。

第一步:直接查看公网实际返回的证书配图

echo | openssl s_client \
  -connect example.com:443 \
  -servername example.com \
  2>/dev/null | openssl x509 -noout \
  -subject -issuer -dates -serial -fingerprint -sha256

将 example.com 替换为实际域名。重点观察:

  • notBefore 和 notAfter:判断公网返回证书的签发及到期时间。
  • subject 或 SAN 中的域名:判断证书是否覆盖当前域名。
  • serial 和 SHA-256 指纹:用于和本地证书进行精确比较。
  • issuer:辅助判断返回的是哪一条证书链。

多域名环境不要只测主域名。可以逐个测试每个域名:

for domain in example.com www.example.com api.example.com; do
  echo "===== $domain ====="
  echo | openssl s_client \
    -connect "${domain}:443" \
    -servername "$domain" \
    2>/dev/null | openssl x509 -noout \
    -subject -issuer -dates -serial -fingerprint -sha256
done

如果不同域名返回不同证书,这是正常的前提之一:它们可能使用不同证书或不同 server 配置。但如果其中一个域名返回了完全不相关的证书,通常是 server_name 没匹配、默认虚拟主机被选中,或配置中存在重复的域名声明。

绕过 DNS 检查指定服务器

如果域名存在多个 A 记录、AAAA 记录,或者前面还有其他 HTTPS 入口,可以使用 curl --resolve 强制把域名解析到指定 IP。这样仍然保留正确的 Host 和 SNI,但不会使用系统 DNS 解析结果。

curl -vkI \
  --resolve example.com:443:203.0.113.10 \
  https://example.com/

这里的 203.0.113.10 仅为示例地址,应替换为香港服务器的实际公网 IPv4。-k 只用于排查阶段让 curl 即使遇到证书错误也继续显示握手过程,不表示生产环境可以忽略证书校验。

如果指定服务器后能看到新证书,而直接访问域名仍是旧证书,问题就不在 Nginx 证书文件本身,应继续核对:

dig +short A example.com
dig +short AAAA example.com

A 记录和 AAAA 记录可能指向不同入口。若用户通过 IPv6 访问,实际请求可能没有到达刚刚检查的 IPv4 服务器,也可能命中了另一份 IPv6 listen 配置。

第二步:确认续期写入的是哪一份证书

以 Certbot 为例,先查看它管理的证书名称、域名和到期时间:

sudo certbot certificates

常见的 Certbot 目录结构如下:

/etc/letsencrypt/live/example.com/fullchain.pem
/etc/letsencrypt/live/example.com/privkey.pem

live 目录中的文件通常是指向 archive 目录的符号链接。查看实际指向:

sudo ls -l /etc/letsencrypt/live/example.com/
sudo readlink -f /etc/letsencrypt/live/example.com/fullchain.pem
sudo readlink -f /etc/letsencrypt/live/example.com/privkey.pem

查看本地证书内容:

sudo openssl x509 \
  -in /etc/letsencrypt/live/example.com/fullchain.pem \
  -noout -subject -issuer -dates -serial -fingerprint -sha256

将这次输出的指纹与前面公网握手得到的指纹比较:

  • 本地和公网指纹相同:证书文件已经被 Nginx 或前置入口使用,问题可能已经解决,继续检查浏览器访问路径或其他域名。
  • 本地指纹较新,公网仍旧:优先检查 Nginx reload、DNS、IPv4/IPv6 和 SNI。
  • 本地指纹仍旧:续期客户端没有真正更新当前证书,或者你查看的是错误的证书目录。
  • 本地日期已更新,但域名不匹配:续期对象可能是另一张证书,需核对 certbot certificates 中的域名列表。

检查 Nginx 是否引用了旧副本

很多“续期成功但 Nginx 不更新”的根因,是 Nginx 配置没有引用 /etc/letsencrypt/live/,而是引用了手工复制的文件,例如:

/etc/nginx/ssl/example.com/cert.pem
/etc/nginx/ssl/example.com/privkey.pem

搜索所有证书配置:

sudo grep -RInE 'ssl_certificate(_key)?' \
  /etc/nginx /etc/letsencrypt/renewal 2>/dev/null

如果 Nginx 使用的是 /etc/nginx/ssl/ 下的固定副本,那么 Certbot 更新 live 目录并不会自动更新这份副本。可以选择以下一种方式修复:

  1. 让 Nginx 直接引用 Certbot 的稳定路径。
  2. 保留独立的 Nginx 证书目录,但在续期成功后使用 --deploy-hook 或 renewal hook 将新文件安装到该目录,再执行 Nginx reload。

不建议每次续期后手工复制证书。手工复制容易漏掉某个域名,也可能只复制了证书而没有同步私钥或证书链。

检查证书和私钥是否匹配

如果修改过证书路径,或者出现 Nginx reload 报错,可以比较证书公钥和私钥公钥的摘要。这个操作不会输出私钥内容:

CERT=/etc/letsencrypt/live/example.com/fullchain.pem
KEY=/etc/letsencrypt/live/example.com/privkey.pem

sudo openssl x509 -in "$CERT" -pubkey -noout \
  | openssl pkey -pubin -outform pem \
  | sha256sum

sudo openssl pkey -in "$KEY" -pubout \
  | sha256sum

两行摘要应当一致。如果不一致,说明证书和私钥不是同一对,Nginx 无法使用这组文件。若私钥加密,命令可能要求输入口令;不要把口令写进脚本、命令历史或自动续期配置。

第三步:以 Nginx 实际加载配置为准

只看某个站点文件不够。Nginx 可能通过 include 引入了其他文件,也可能在 conf.d、sites-enabled 中存在同域名的重复配置。使用 nginx -T 查看 Nginx 当前会读取的完整配置:

sudo nginx -T > /tmp/nginx-full.conf 2>/tmp/nginx-full-error.log

然后搜索域名、监听端口和证书路径:

grep -nE 'listen .*443|server_name|ssl_certificate(_key)?' \
  /tmp/nginx-full.conf

也可以直接搜索配置目录:

sudo grep -RInE \
  'server_name|listen .*443|ssl_certificate(_key)?' \
  /etc/nginx/sites-enabled /etc/nginx/conf.d 2>/dev/null

重点确认以下几项:

  • 目标域名只出现在预期的 HTTPS server 块中。
  • ssl_certificate 使用的是新证书所在路径。
  • ssl_certificate_key 与证书属于同一域名和同一证书版本。
  • 多域名的 server_name 没有拼写错误、漏写或多余通配符。
  • IPv4 和 IPv6 的 listen 443 配置没有指向另一份站点配置。
  • 站点文件确实被 include,而不是只存在于未启用目录中。

一个使用 Certbot 路径的示例配置如下:

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name example.com www.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
    }
}

fullchain.pem 包含站点证书和中间证书,通常比只配置 cert.pem 更适合直接提供给客户端。若使用独立证书目录,也应确保该目录中的证书文件包含完整链,并且路径在续期后仍然保持稳定。

多域名和 SNI 的典型冲突

HTTPS 在同一个 IP 和 443 端口上承载多个域名时,Nginx 主要依靠 SNI 选择证书。以下情况容易造成“只有一个域名不生效”:

多域名和 SNI 的典型冲突配图

  • server_name 中漏掉了访问域名。
  • 同一个域名出现在多个 server 块中。
  • 某个 server 块被设置为 default_server,但目标域名没有匹配到其他配置。
  • 访问的是 www.example.com,配置只写了 example.com。
  • IPv6 入口使用了不同的 Nginx 配置。
  • 测试命令没有加 -servername,导致测试结果与浏览器行为不同。

如果每个域名使用独立证书,可以采用多个精确匹配的 server 块:

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
    }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name api.example.com;

    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:9000;
    }
}

如果多个域名由同一张包含这些域名的 SAN 证书覆盖,也可以放在同一个 server_name 中。但不能因为两个域名都使用 HTTPS,就把域名 A 的证书随意配置到域名 B 的独立站点上。

nginx -t 能检查语法和文件可读性,但不能证明公网请求一定会命中正确的 server。因此,配置检查通过后,还必须使用带 -servername 的 OpenSSL 命令分别验证每个域名。

第四步:测试并执行安全 reload

修改配置或路径后,先执行测试:

sudo nginx -t

成功时通常会看到类似以下结果:

syntax is ok
test is successful

如果失败,常见含义如下:

错误类型可能原因处理方向
cannot load certificate路径错误、文件不存在或权限不足检查 readlink -f、文件权限和目录权限
PEM_read_bio_X509证书文件内容损坏或格式不对查看证书头部,确认是 PEM 文件
key values mismatch证书和私钥不匹配重新绑定同一张证书和私钥
duplicate listen options多个配置对同一监听端口声明冲突检查重复的 listen 443 参数
duplicate server name同一监听地址上存在重复域名合并或修正重复的 server_name
permission deniedNginx worker 无法读取证书或私钥检查目录遍历权限和文件权限

只有测试成功后,才执行 reload:

sudo nginx -t && sudo systemctl reload nginx

reload 后确认服务仍处于运行状态:

sudo systemctl is-active nginx
sudo systemctl status nginx --no-pager

查看 reload 是否被服务管理器接受:

sudo journalctl -u nginx --since "10 minutes ago" --no-pager

如果 nginx -t 失败,不要直接使用 systemctl restart nginx 强行重启。重启可能扩大配置错误的影响范围,甚至导致原本仍可用的 HTTPS 服务无法恢复。应先恢复出错的那一个配置文件,重新执行 nginx -t,通过后再 reload。

第五步:检查自动续期后的 reload 钩子

证书自动续期和 Nginx 自动加载是两个独立动作。ACME 客户端完成证书更新后,如果没有执行 reload,Nginx 进程不会主动重新读取证书文件。

Certbot 使用 renewal hook

可以建立一个只负责检查配置并执行优雅 reload 的脚本:

#!/bin/sh
set -eu

/usr/sbin/nginx -t
/bin/systemctl reload nginx
/usr/bin/logger -t certbot-nginx-reload "nginx reloaded after certificate renewal"

将脚本保存为:

/etc/letsencrypt/renewal-hooks/deploy/20-nginx-reload

然后设置为仅允许管理员执行:

sudo chmod 750 /etc/letsencrypt/renewal-hooks/deploy/20-nginx-reload
sudo chown root:root /etc/letsencrypt/renewal-hooks/deploy/20-nginx-reload

Certbot 的 deploy hook 只应在证书实际成功续期后触发,避免定时检查每次都 reload Nginx。确认目录中存在该脚本:

sudo ls -l /etc/letsencrypt/renewal-hooks/deploy/

查看 Certbot 的定时任务和执行记录:

systemctl list-timers --all | grep -i certbot
sudo journalctl -u certbot --since "7 days ago" --no-pager

不同发行版可能使用 certbot.service、certbot.timer 或其他任务名称。如果没有对应 systemd 单元,还要检查实际运行用户的 cron 配置:

sudo crontab -l

测试时可以先单独确认 reload 脚本是否正常:

sudo /etc/letsencrypt/renewal-hooks/deploy/20-nginx-reload

这不会申请新证书,只会执行配置检查和 reload。若脚本失败,先修复脚本中的 Nginx 路径、权限或服务名称,再进行续期测试。

Certbot 的续期模拟命令如下:

sudo certbot renew --dry-run

该命令通常使用测试环境验证续期流程,不应将测试结果等同于生产证书已经更新。重点观察挑战验证、证书保存和 hook 执行日志。不要为了“立即更新”频繁重复申请生产证书,以免触发证书颁发机构的频率限制。

使用 acme.sh 时保持路径一致

如果服务器使用 acme.sh,不要同时让 Certbot 和 acme.sh 管理同一个域名的同一套证书。acme.sh 的工作目录和运行用户可能不同,先查看当前管理列表:

~/.acme.sh/acme.sh --list

建议将证书安装到 Nginx 使用的稳定路径,并设置 reload 命令。例如:

~/.acme.sh/acme.sh --install-cert -d example.com \
  --key-file /etc/nginx/ssl/example.com/privkey.pem \
  --fullchain-file /etc/nginx/ssl/example.com/fullchain.pem \
  --reloadcmd "systemctl reload nginx"

执行前确认目标目录已经创建,并根据实际运行用户设置适当的目录权限。此命令会更新目标文件,影响的是指定域名的证书路径,不应把路径替换成生产环境中其他站点的目录。

安装后检查 Nginx 配置是否确实引用了这两个目标文件:

sudo grep -RInE 'ssl_certificate(_key)?' /etc/nginx

如果 acme.sh 更新了自己的证书目录,但 Nginx 引用的是 /etc/nginx/ssl/,而没有执行 --install-cert,就会出现“续期成功、Nginx仍然显示旧证书”的现象。

根据结果选择修复分支

完成外部证书、本地文件和 Nginx 配置检查后,可以按下面的结果处理:

本地新、公网旧,且所有域名都旧

优先执行:

sudo nginx -t
sudo systemctl reload nginx
sudo journalctl -u nginx --since "15 minutes ago" --no-pager

如果 reload 成功但公网仍旧,继续检查:

dig +short A example.com
dig +short AAAA example.com
sudo ss -lntp | grep ':443'

然后分别使用 IPv4 地址和 IPv6 入口测试。此时重点不是重复申请证书,而是确认访问路径是否仍然指向这台香港服务器,以及 443 端口是否由预期的 Nginx 进程处理。

本地仍旧,Certbot 或 acme.sh 日志显示没有更新

先查看证书客户端实际管理的域名和路径:

sudo certbot certificates
sudo ls -l /etc/letsencrypt/live/

如果是 acme.sh,则使用实际签发用户执行:

~/.acme.sh/acme.sh --list

此类问题可能与挑战验证、任务未到续期时间、权限、磁盘空间或续期配置中的域名不一致有关。先修复续期任务,不要直接修改 archive 或手动删除历史证书目录。Certbot 的 live、archive 和 renewal 目录存在关联关系,手工删除可能让后续续期任务无法运行。

只有一个域名旧,其他域名新

重点检查这个域名的:

  • server_name 是否准确。
  • ssl_certificate 是否引用了自己的证书路径。
  • ssl_certificate_key 是否匹配。
  • 是否存在另一个相同 server_name 的配置。
  • 是否通过 IPv6 命中了另一份配置。
  • 测试时是否正确使用了 -servername。

可以针对单个域名执行:

echo | openssl s_client \
  -connect api.example.com:443 \
  -servername api.example.com \
  2>/dev/null | openssl x509 -noout \
  -subject -dates -fingerprint -sha256

如果这里返回的证书与 nginx -T 中该域名配置的证书不一致,说明当前实际加载的配置和你正在编辑的文件不是同一份。

日期正确,但客户端报告证书链错误

检查 Nginx 是否配置了完整链:

sudo openssl x509 \
  -in /path/to/configured/fullchain.pem \
  -noout -subject -issuer

同时查看配置是否误用了:

ssl_certificate /path/to/cert.pem;

如果客户端需要完整证书链,通常应改为:

ssl_certificate /path/to/fullchain.pem;

私钥仍使用对应的 privkey.pem。修改后必须重新执行 nginx -t 和 reload。

修复后的完整验证

不要只看 systemctl reload nginx 返回成功。建议按以下顺序进行复测。

1. 确认实际加载的路径

sudo nginx -T 2>/dev/null \
  | grep -nE 'server_name|ssl_certificate(_key)?|listen .*443'

确认每个域名都关联到预期证书和私钥。

2. 比较本地证书指纹

sudo openssl x509 \
  -in /etc/letsencrypt/live/example.com/fullchain.pem \
  -noout -dates -serial -fingerprint -sha256

如果使用的是独立安装目录,将路径替换为 Nginx 配置中的实际路径。

3. 建立新的公网 TLS 连接

echo | openssl s_client \
  -connect example.com:443 \
  -servername example.com \
  2>/dev/null | openssl x509 -noout \
  -subject -issuer -dates -serial -fingerprint -sha256

公网指纹应与 Nginx 实际引用的本地证书一致。多个域名要分别验证,不能用主域名的结果代替其他域名。

4. 复查 HTTPS 请求

curl -I https://example.com/
curl -I https://www.example.com/
curl -I https://api.example.com/

如果业务接口不支持 HEAD,可以改用:

curl -vk https://api.example.com/ -o /dev/null

这里主要观察 TLS 握手和证书信息,不要求接口一定返回特定 HTTP 状态码。

5. 观察后续一次自动续期

确认自动续期任务运行时具备以下闭环:

  1. ACME 客户端成功更新证书。
  2. 新证书写入 Nginx 配置引用的路径。
  3. deploy hook 被调用。
  4. nginx -t 成功。
  5. systemctl reload nginx 成功。
  6. 新连接获取到更新后的证书。

可以通过日志确认 hook 是否运行:

sudo journalctl -t certbot-nginx-reload --since "30 days ago" --no-pager

如果没有日志,说明 hook 没有被执行,或日志命令路径与系统实际路径不一致。此时应检查 hook 文件的执行权限、Certbot 运行用户和定时任务来源。

证书自动续期后的 Nginx 未生效,最常见的修复并不是重新签发证书,而是让“证书存储路径、Nginx实际加载路径、reload钩子、域名SNI匹配”四者保持一致。对于香港服务器上的多域名 HTTPS 部署,先用带 -servername 的外部握手确认实际返回结果,再用 nginx -T 找到真正生效的配置,最后以 nginx -t && systemctl reload nginx 完成低风险切换,通常比直接重启或反复申请证书更容易定位问题。

目录结构
全文