Nginx / 网站服务

Nginx ACME 证书更新后随机返回过期证书 – 问题排查与解决方案

浅时光博客 · 2月27日 · 2026年 · 17 次已读

一、问题现象


1.1 问题描述

ACME (Let’s Encrypt) 证书成功续期后,客户端访问网站时随机返回过期证书

✅ 证书文件已确认更新:
   /usr/local/nginx/conf/cert.pem → notAfter=May 27 2026(新证书)

❌ 但客户端访问时随机返回旧证书:
   • openssl 测试:20 次请求中 ~35% 返回 notAfter=Feb 18 2026(旧证书)
   • 浏览器访问:偶尔提示"您的连接不是私密连接" / "证书已过期"
   • myssl.com 检测:同时显示"已过期"和"可信"两条矛盾记录

1.2 关键特征

特性说明
随机性同一设备多次刷新,证书状态”跳变”
文件正确本地证书文件 cert.pem 已更新为新证书
进程冲突多个 Nginx 进程同时监听 443 端口
时间关联旧证书过期时间 = 2026-02-18,问题出现在续期后

二、排查过程


2.1 阶段一:确认证书文件状态

# 验证服务器返回的证书(带 SNI)
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates

# 输出示例(随机出现):
# notBefore=Nov 20 11:20:38 2025 GMT
# notAfter=Feb 18 11:20:37 2026 GMT    ❌ 旧证书

# 或
# notBefore=Feb 26 09:53:46 2026 GMT
# notAfter=May 27 09:53:45 2026 GMT    ✅ 新证书


# 验证本地证书文件内容
openssl x509 -in /usr/local/nginx/conf/cert.pem -noout -subject -enddate -issuer

# 输出(始终正确):
subject=CN=example.com
notAfter=May 27 09:53:45 2026 GMT    ✅ 新证书
issuer=C=US, O=Let's Encrypt, CN=E7
  • 结论:证书文件已正确更新,问题出在 Nginx 进程加载逻辑

2.2 阶段二:检查 Nginx 进程状态

# 查看 Nginx 进程树(含启动时间)
ps -eo pid,ppid,cmd,lstart | grep nginx | grep -v grep
  • 发现关键问题
# ❌ 旧实例(2025-11-20 启动,持有旧证书内存)
root  2743757  ...  nginx: master process nginx -
├─ 2104488 2743757 nginx: worker process   Sun Nov 30 11:50:56 2025

# ✅ 新实例(2026-02-26 启动,持有新证书内存)  
root  2895550  ...  nginx: master process /usr/local/nginx/sbin/nginx
├─ 2895551 2895550 nginx: worker process   Thu Feb 26 22:34:17 2026

# 查看 443 端口监听情况
ss -tlnp | grep ":443"

# 输出示例:
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=2899455,fd=10),("nginx",pid=2899454,fd=10))
  • 结论:两个独立的 Nginx master 进程同时监听 443 端口,请求被内核随机分发!

2.3 阶段三:检查 systemd 配置

# 查看 systemd 服务配置
cat /usr/lib/systemd/system/nginx.service

[Unit]
Description=nginx - high performance web server
Documentation=http://nginx.org/en/docs/
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
ExecStartPre=/usr/local/nginx/sbin/nginx -t
ExecStart=/usr/local/nginx/sbin/nginx
ExecReload=/usr/local/nginx/sbin/nginx -s reload
ExecStop=/usr/local/nginx/sbin/nginx -s quit
PrivateTmp=true

[Install]
WantedBy=multi-user.target
  • 发现配置问题原文链接:https://dqzboy.com缺少 PIDFile + ExecStop 依赖 pid 文件
Type=forking
ExecStartPre=/usr/local/nginx/sbin/nginx -t
ExecStart=/usr/local/nginx/sbin/nginx
ExecReload=/usr/local/nginx/sbin/nginx -s reload
ExecStop=/usr/local/nginx/sbin/nginx -s quit   # ❌ 关键问题
PrivateTmp=true

参数详细解析

配置项当前写法问题后果
Type=forking✅ 正确nginx 启动时 fork,master 退出,worker 接管需要 PIDFile 跟踪
PIDFile缺失systemd 无法知道 master 进程的 PIDstop/restart 找不到目标进程
ExecStop-s quit依赖 nginx -s quit 读取 pid 文件如果 pid 文件不存在/路径错,命令静默失败
ExecReload-s reload同样依赖 pid 文件重载可能发送到错误进程或失败

核心原因链

❌ 直接原因:
   两个 Nginx master 进程同时监听 443 端口 → 请求随机分发 → 随机返回旧/新证书

❌ 深层原因:
   1. systemd 配置缺失 PIDFile → 无法跟踪手动编译版 nginx 进程
   2. systemd 配置路径指向 RPM 版,与实际运行版本不匹配
   3. 手动执行 nginx 二进制 + systemctl 混用 → 产生多实例冲突
   4. Nginx worker 启动时将证书加载到内存 → 文件更新不会自动刷新

✅ 修复核心:
   • 统一通过 systemd 管理 nginx 启停
   • 修正 systemd 配置路径,匹配手动编译版实际路径
   • 添加 PIDFile 确保 systemd 能正

四、解决方案


4.1 步骤一:清理残留进程

① 尝试优雅停止
systemctl stop nginx 2>/dev/null
/usr/local/nginx/sbin/nginx -s quit 2>/dev/null

② 等待 3 秒
sleep 3

③ 强制杀死残留进程
pkill -9 nginx

④ 确认端口已释放
if ss -tlnp | grep -q ":443.*nginx"; then
    echo "警告:443 端口仍有 nginx 进程监听,尝试 fuser 强制释放..."
    fuser -k 443/tcp 2>/dev/null
    sleep 2
fi

⑤ 最终验证
if ss -tlnp | grep -q ":443.*nginx"; then
    echo "失败:仍有进程监听 443 端口,请手动检查"
    ss -tlnp | grep ":443"
    exit 1
else
    echo "成功:443 端口已释放"
fi
原文链接:https://dqzboy.com

4.2 步骤二:修正 systemd 配置(关键!)

# 备份原配置
cp /usr/lib/systemd/system/nginx.service /usr/lib/systemd/system/nginx.service.bak.$(date +%F)

# 写入新配置(适配手动编译版 nginx)
cat > /usr/lib/systemd/system/nginx.service << 'EOF'
[Unit]
Description=nginx (manual compile) - high performance web server
Documentation=http://nginx.org/en/docs/
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
#关键:PID 文件路径(必须与 nginx.conf 中的 pid 指令一致)
PIDFile=/usr/local/nginx/logs/nginx.pid
# 环境变量:指定配置文件路径
Environment="conffile=/usr/local/nginx/conf/nginx.conf"
# 启动前测试配置,失败则不启动
ExecStartPre=/usr/local/nginx/sbin/nginx -t
# 启动命令:指向手动编译版二进制
ExecStart=/usr/local/nginx/sbin/nginx -c ${conffile}
# 重载配置:使用 $MAINPID + HUP 信号
ExecReload=/bin/kill -s HUP $MAINPID
# 优雅停止:使用 QUIT 信号让 worker 处理完请求再退出
ExecStop=/bin/kill -s QUIT $MAINPID
# 进程退出超时时间(nginx 优雅退出可能需要 10-30 秒)
TimeoutStopSec=30
# 崩溃时自动重启(生产环境强烈建议)
Restart=on-failure
RestartSec=5s
# 资源限制:高并发场景优化
LimitNOFILE=65535
LimitNPROC=65535
# 安全选项
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF

4.3 步骤三:确认 nginx.conf 中的 pid 路径匹配

# 检查 nginx.conf 中的 pid 指令
grep "^pid" /usr/local/nginx/conf/nginx.conf

# 应输出:pid /usr/local/nginx/logs/nginx.pid;
# 如果路径不同,请修改 nginx.conf:
#   sed -i 's|^pid .*|pid /usr/local/nginx/logs/nginx.pid;|' /usr/local/nginx/conf/nginx.conf

4.4 步骤四:通过 systemd 统一启动

systemctl daemon-reload

systemctl enable nginx
systemctl start nginx

echo "验证服务状态..."
systemctl status nginx --no-pager -l

# 验证进程唯一性
MASTER_COUNT=$(ps aux | grep "nginx: master" | grep -v grep | wc -l)
if [ "$MASTER_COUNT" -eq 1 ]; then
    echo "Nginx master 进程唯一"
else
    echo "发现 $MASTER_COUNT 个 master 进程!"
    ps aux | grep "nginx: master" | grep -v grep
fi

# 验证端口监听
PORT_COUNT=$(ss -tlnp | grep ":443" | grep nginx | wc -l)
if [ "$PORT_COUNT" -eq 1 ]; then
    echo "443 端口监听唯一"
else
    echo "警告:$PORT_COUNT 个进程监听 443 端口!"
    ss -tlnp | grep ":443"
fi

五、验证修复效果


5.1 基础验证脚本

  • 简单的for循环20次检查本地证书
for i in {1..20}; do   result=$(echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -enddate);   echo "$result" | grep -q "May 27" && echo "✅ $i: OK" || echo "❌ $i: $result"; done

5.2 第三方检测

预期结果

  • 信任状态:✅ 可信
  • 证书有效期:2026-02-26 ~ 2026-05-27
  • 无”已过期”或”证书不匹配”警告

八、常用命令


8.1 证书相关

# 查看服务器返回的证书
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates

# 查看本地证书文件
openssl x509 -in /usr/local/nginx/conf/cert.pem -noout -text | head -20

# 检查证书剩余天数
openssl x509 -in /usr/local/nginx/conf/cert.pem -noout -enddate | cut -d= -f2 | xargs -I{} date -d "{}" +%s | xargs -I{} bash -c 'echo $(( ({} - $(date +%s)) / 86400 )) days left'

8.2 端口占用

# 检查 443 端口被哪些进程占用
ss -tlnp | grep ":443"
lsof -i :443

# 强制释放端口(谨慎使用)
fuser -k 443/tcp

# 检查 SELinux 是否阻止 nginx 读取证书
ls -Z /usr/local/nginx/conf/cert.pem
ausearch -m avc -ts recent | grep nginx

# 临时关闭 SELinux 测试(生产环境慎用)
setenforce 0
# 测试后恢复
setenforce 1

本文作者:浅时光博客
原文链接:https://www.dqzboy.com/19092.html
版权声明:知识共享署名-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)协议进行许可,转载时请以>超链接形式标明文章原始出处和作者信息
免责声明:本站内容仅供个人学习与研究,严禁用于商业或非法目的。请在下载后24小时内删除相应内容。继续浏览或下载即表明您接受上述条件,任何后果由用户自行承担。