ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Let’s Encrypt自动续期失效的五大原因与修复方案

Let’s Encrypt自动续期失效的五大原因与修复方案 1. 这不是玄学是可验证的机制Let’s Encrypt 证书自动续期到底靠不靠谱Let’s Encrypt、certbot、自动续期、ACME、Nginx——这五个词凑在一起几乎就是现代 Web 运维人的日常呼吸节奏。但凡你亲手配过 HTTPS十有八九在某个凌晨三点被一封“您的域名证书将在 2 天后过期”的邮件惊醒然后一边揉眼睛一边打开终端敲sudo certbot renew --dry-run。这种条件反射式的操作背后藏着一个被广泛误读却极少被真正验证的核心问题Let’s Encrypt 的证书到底会不会自动续期答案不是“会”或“不会”而是它一定会尝试续期但成功与否完全取决于你部署时埋下的每一个细节——而这些细节90% 的人根本没检查过。我最近刚完成一次完整的服务器迁移从一台老旧的 CentOS 7 物理机迁移到全新的 Ubuntu 22.04 云实例。所有服务Nginx、PHP-FPM、MySQL都按原配置重建防火墙、SELinux、系统时间全部校准连/etc/hosts里那行127.0.0.1 localhost都手动核对了三遍。结果上线第三天用户开始反馈“网站打不开”浏览器报错NET::ERR_CERT_EXPIRED。查日志发现 Nginx 启动失败错误指向 SSL 证书路径不可读进目录一看/etc/letsencrypt/live/example.com/fullchain.pem文件时间戳竟然是 90 天前——也就是上一台服务器上签发的旧证书新机器上压根没触发过任何续期动作。这不是运气差是典型的“自动续期幻觉”。很多人以为只要装了 certbot、跑过certbot --nginx系统就自动接管了一切。实际上certbot 的“自动”二字只覆盖了续期逻辑本身而触发、执行、验证、部署、通知这五个环节每个都可能在迁移、重装、权限变更、cron 调度异常时无声断裂。更讽刺的是Let’s Encrypt 官方文档明确写着“certbot 不会自动重启你的 Web 服务”但绝大多数教程却把--nginx参数当成万能开关忽略了它背后依赖的 Nginx 配置钩子、systemd timer 状态、文件权限继承链等一整套脆弱的协同机制。这篇文章不讲理论不堆命令只复盘这次排查全过程从发现证书失效到定位 cron 任务缺失从修复 systemd timer 权限到验证 ACME 挑战能否被 Nginx 正确转发再到确认ip头部的五元组信息 nginx转发会带吗这个看似无关的问题如何真实影响了.well-known/acme-challenge的 HTTP-01 验证成功率。我会把每一步的why和how拆开给你看包括那些藏在journalctl -u certbot.timer日志里、连官方 debug 模式都不提示的隐性陷阱。如果你正在用 CentOS 7 或 Ubuntu 系统如果你的 Nginx 配置里还混着location ~ /\.well-known的粗暴 deny 规则或者你根本不知道lets encrypt isrg root x1 最低支持 java jdk 内置版本是多少这个问题其实和你的前端 JS 加载无关——那你真的该停下来花 20 分钟把这篇实操记录读完。它不是教你怎么装 certbot而是告诉你当自动续期失效时你该往哪个方向、用哪条命令、查哪行日志、改哪处配置才能在 5 分钟内让证书重新活过来。2. 自动续期的真相不是“开箱即用”而是“五环咬合”2.1 自动续期不是 certbot 的默认行为而是 systemd cron ACME 协议的精密协作很多人以为certbot renew命令本身具备“定时能力”这是最大的认知偏差。certbot 本身只是一个 ACME 协议客户端它的核心职责只有两件事发起挑战challenge和提交签名finalize。所谓“自动续期”本质是操作系统层面的调度器systemd timer 或 cron定期调用 certbot并由 certbot 根据本地证书状态决定是否执行续期流程。这个链条缺一环整个自动机制就瘫痪。我们来拆解这个“五环咬合”结构触发层TriggerLinux 系统的定时任务调度器。在较新系统Ubuntu 18.04、CentOS 8中certbot 默认注册为 systemd timercertbot.timer每 12 小时检查一次在旧系统CentOS 7、Debian 9中则依赖/etc/cron.d/certbot文件中的 cron 表达式通常是0 */12 * * * root test -x /usr/bin/certbot perl -e sleep int(rand(3600)) certbot -q renew。注意那个perl -e sleep int(rand(3600))——它不是为了“随机延迟”而是为了避免全球数百万服务器在同一秒向 Let’s Encrypt 发起 renewal 请求导致 ACME 服务器过载。这个 sleep 是强制的且必须存在。执行层Executorcertbot 二进制本身。它读取/etc/letsencrypt/renewal/下的.conf文件如example.com.conf解析其中的[[webroot]]或[[nginx]]插件配置确定使用哪种验证方式HTTP-01 或 TLS-ALPN-01并检查证书剩余有效期是否小于 30 天默认阈值可通过--renew-by-default强制续期。验证层VerifierACME 协议的挑战响应阶段。以最常见的 HTTP-01 为例certbot 会生成一个临时 token 文件存入/var/www/html/.well-known/acme-challenge/webroot 方式或通过 Nginx 的location规则动态返回nginx 插件方式。关键点在于这个请求必须能被 Let’s Encrypt 的验证服务器acme-v02.api.letsencrypt.org从公网直接访问到且返回的响应必须严格匹配 ACME 协议要求的格式纯文本无 HTML 标签无重定向HTTP 状态码 200。部署层Deployer证书续期成功后certbot 需要将新证书写入/etc/letsencrypt/live/目录并更新软链接。但更重要的是它需要通知 Web 服务重新加载证书。--nginx参数的作用就是调用 Nginx 的reload命令nginx -s reload而非restart。这里有个致命细节reload只生效于 Nginx 主进程有权限读取新证书文件的场景。如果/etc/letsencrypt/live/目录的属主是root:root而 Nginx worker 进程以www-data或nginx用户运行那么reload后 Nginx 仍会继续使用旧证书缓存直到你手动restart——这就是为什么很多“自动续期成功”的日志里Nginx 实际并未生效。通知层Notifiercertbot 的--deploy-hook或--renew-hook参数。它们允许你在续期成功后执行自定义脚本比如发送 Slack 通知、更新 CDN 证书、或执行systemctl reload nginx比--nginx更底层、更可控。但绝大多数一键安装脚本如certbot --nginx根本不会帮你配置这个钩子导致续期成功却无人知晓故障发生时只能靠用户投诉才发现。提示你可以用systemctl list-timers --all | grep certbot快速查看 systemd timer 状态用crontab -l -u root | grep certbot检查 cron 是否存在。两者不能共存否则会造成重复执行或冲突。2.2 为什么服务器迁移后自动续期必然失效三个隐形断点服务器迁移不是简单的文件拷贝它会彻底切断上述五环中的至少三个关键连接点。我在 CentOS 7 → Ubuntu 22.04 迁移中就踩中了全部三个断点一systemd timer 未随 certbot 包自动启用Ubuntu 22.04 的 certbot 包来自 universe 源安装后certbot.timer是 disabled 状态。它不像旧版那样自动 enable也不像某些第三方 PPA 那样预设启动。你必须手动执行sudo systemctl enable --now certbot.timer。而绝大多数迁移文档只教你apt install certbot python3-certbot-nginx漏掉了这关键一步。systemctl status certbot.timer显示inactive (dead)意味着触发层从第一天起就已瘫痪。断点二Nginx 插件配置未继承HTTP-01 挑战路径被拦截旧服务器上certbot 使用--nginx时会在/etc/letsencrypt/renewal/example.com.conf中写入installer nginx和authenticator nginx。但新服务器上即使你用相同命令重装certbot 也不会自动复用旧配置——它会创建一个全新的.conf文件且默认使用webroot方式因为检测不到 Nginx 的server块中是否有location ^~ /.well-known/acme-challenge/规则。更麻烦的是很多 Nginx 安全加固模板尤其是从网络下载的“最佳实践”配置会包含类似location ~ /\. { deny all; }的规则它会暴力拦截所有以.开头的路径包括.well-known/acme-challenge/xxx。ACME 验证请求进来直接返回 403挑战失败续期终止。断点三证书文件权限与 Nginx worker 用户不匹配CentOS 7 默认 Nginx 进程用户是nginxUbuntu 22.04 默认是www-data。而/etc/letsencrypt/目录的权限是755 root:root证书文件是644 root:root。这意味着www-data用户可以读取证书因为 group 是 root且有 r 权限但nginx用户在 CentOS 7 上可能被排除在 root 组外。迁移后如果你没手动user www-data;修改 Nginx 配置或没给www-data用户添加 root 组权限reload后 Nginx 就会因无法读取新证书而回退到旧证书甚至启动失败。这三个断点任何一个都会让“自动续期”变成一句空话。而它们都不会在certbot renew --dry-run中暴露——因为 dry-run 只模拟执行不真正写入文件、不触发 Nginx reload、不检查权限继承。它只会告诉你“所有证书都有效”却不会告诉你“下次真续期时Nginx 会因 403 错误而失败”。3. 实操排查从日志到命令五分钟定位失效根源3.1 第一步确认续期任务是否被触发看 timer/cron不要猜直接查。这是最快速排除“根本没动”的方法。在 Ubuntu 22.04 上# 查看 certbot timer 状态和上次触发时间 sudo systemctl status certbot.timer # 输出示例Active: active (waiting) since Mon 2024-05-20 14:30:22 CST; 1 day 5h ago # 如果显示 inactive (dead)立刻启用 sudo systemctl enable --now certbot.timer # 查看 timer 的详细日志重点看 Last Trigger sudo systemctl list-timers --all | grep certbot # 输出示例Sat 2024-05-20 14:30:22 CST 1 day 5h left 12h ago certbot.timer # 进一步查看 timer 对应 service 的执行日志 sudo journalctl -u certbot.service -n 50 --no-pager # 关键线索Look for Renewal configuration file and Cert not yet due for renewal在 CentOS 7 上# 检查 cron 是否存在且语法正确 sudo crontab -l -u root | grep certbot # 正常输出应包含0 */12 * * * root test -x /usr/bin/certbot perl -e sleep int(rand(3600)) certbot -q renew # 手动模拟 cron 执行加 -v 参数看详细过程 sudo /usr/bin/certbot -q renew --dry-run -v 21 | grep -E (Attempting|Renewal|Challenge|error) # 注意--dry-run 会跳过实际写入但会完整走完验证流程实操心得certbot -q renew --dry-run的输出里如果看到No renewals were attempted别急着认为“没问题”。这通常意味着 certbot 认为所有证书有效期 30 天所以跳过。你需要强制测试sudo certbot renew --force-renewal --dry-run。但注意Let’s Encrypt 对同一域名有速率限制每周 5 次生产环境慎用。3.2 第二步验证 ACME 挑战能否被公网访问测 HTTP-01这是最常被忽略的环节。certbot renew --dry-run成功不代表真实环境能通。我们必须模拟 Let’s Encrypt 验证服务器的视角。原理很简单Let’s Encrypt 的验证服务器会向http://yourdomain.com/.well-known/acme-challenge/xxxxx发起 GET 请求期望得到一个纯文本响应内容是 token key authorization 的 hash。如果这个请求被 Nginx 返回 403、404、301 重定向或响应体里混入了 HTML 标签、空格、换行符挑战就失败。实操四步法找到当前待续期证书的 challenge token# 查看 renewal 配置确认使用哪种 authenticator sudo cat /etc/letsencrypt/renewal/example.com.conf | grep authenticator # 如果是 nginxcertbot 会动态注入 location 规则无需手动配置如果是 webroot需确认 webroot-path # 手动触发一次 renewal 并观察日志不加 --dry-run sudo certbot renew --force-renewal -v 21 | grep -A5 Performing the following challenges # 输出示例Performing the following challenges: # http-01 challenge for example.com # Waiting for verification... # Cleaning up challenges # Failed to renew certificate example.com with error: Some challenges have failed. # 在 Waiting for verification... 后certbot 会短暂生成 token 文件路径类似 /var/lib/letsencrypt/http_challenges/xxxxx从服务器本地 curl 测试# 模拟 Let’s Encrypt 请求注意必须用域名不能用 IP curl -I http://example.com/.well-known/acme-challenge/test123 # 正常应返回HTTP/1.1 200 OK # 如果返回 403/404说明 Nginx 规则有问题 # 检查 Nginx 是否监听了 80 端口且无重定向 sudo ss -tlnp | grep :80 # 检查是否有 server { listen 80; return 301 https://$host$request_uri; } 导致重定向 sudo nginx -T | grep -A10 server.*80从公网第三方工具验证使用 https://check-your-website.net 或 https://www.sslshopper.com/ssl-checker.html 输入你的域名它会从全球多个节点发起 HTTP-01 挑战探测并给出详细的响应头和 body 分析。这是最接近 Let’s Encrypt 真实环境的测试。检查 Nginx 的 .well-known location 规则# 查看 Nginx 是否有针对 .well-known 的显式配置 sudo nginx -T | grep -A10 \.well-known # 正常应看到类似 # location ^~ /.well-known/acme-challenge/ { # root /var/www/html; # default_type text/plain; # } # 如果看到 location ~ /\. { deny all; }这就是罪魁祸首必须注释或修改为更精确的匹配。注意ip头部的五元组信息 nginx转发会带吗这个问题在此刻变得关键。Nginx 作为反向代理时如果上游是另一个 Web 服务如 Node.js它默认会透传客户端 IPviaX-Forwarded-For但原始 TCP 五元组源IP:源端口 - 目标IP:目标端口 协议在应用层已不可见。ACME 验证服务器只关心 HTTP 层响应不校验 TCP 层信息所以这个问题对 Let’s Encrypt 本身无影响。但它会影响你自己的 WAF 或安全组策略——如果安全组只放行了特定 IP 段的 80 端口访问而 Let’s Encrypt 的验证 IP 段目前主要是172.65.0.0/16,104.28.0.0/16等 Cloudflare 段未被放行挑战同样会失败。因此务必确认你的防火墙iptables/nftables/ucloud 安全组对 80 端口是全网开放的。3.3 第三步确认证书部署与 Nginx 加载是否生效查文件与进程即使挑战成功证书写入和 Nginx 加载也可能失败。这是“续期成功但网站仍报错”的常见原因。文件层验证# 查看 live 目录下证书的最后修改时间 ls -la /etc/letsencrypt/live/example.com/ # 正常应看到 fullchain.pem 和 privkey.pem 的时间戳是几分钟内续期刚完成 # 如果时间戳是 90 天前说明续期根本没执行或执行后被覆盖回旧文件 # 检查 renewal 配置中指定的证书路径是否与 Nginx 配置一致 sudo nginx -T | grep ssl_certificate # 输出示例ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # 确保这两个路径与 live 目录下的软链接目标一致用 ls -l 查看 # 检查证书文件的实际内容确认不是空文件或旧内容 sudo openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -text -noout | grep Not After # 输出应显示未来 90 天的日期而非过去日期进程层验证# 查看 Nginx 主进程的启动时间reload 不会改变启动时间restart 才会 sudo ps aux | grep nginx | grep master # 输出示例root 1234 0.0 0.1 123456 7890 ? S May20 0:05 nginx: master process /usr/sbin/nginx # 如果启动时间是昨天说明 reload 成功如果是 3 天前说明上次 reload 失败Nginx 仍在用旧内存缓存 # 强制 reload 并观察错误 sudo nginx -s reload # 如果报错nginx: [emerg] SSL_CTX_use_PrivateKey_file(/etc/letsencrypt/live/example.com/privkey.pem) failed (SSL: error:0B080074:x509 certificate routines:X509_check_private_key:key type mismatch) # 这说明 privkey.pem 文件损坏或与 fullchain.pem 不匹配需重新签发 # 查看 Nginx error.log 中关于 SSL 的最新错误 sudo tail -n 20 /var/log/nginx/error.log | grep -i ssl # 关键错误SSL_CTX_use_PrivateKey_file failed, SSL_do_handshake() failed, no suitable key share实操心得Nginx 的 SSL 证书加载是“懒加载”机制。它只在新连接建立时才读取证书文件。所以nginx -s reload后旧连接仍用旧证书新连接才用新证书。这意味着你用curl -I https://example.com测试时如果看到旧证书信息别慌——多试几次或等几秒再测。真正的验证是openssl s_client -connect example.com:443 -servername example.com 2/dev/null | openssl x509 -noout -dates它会强制建立新 SSL 连接。4. 核心修复方案三步落地永绝后患4.1 修复触发层统一启用 systemd timer推荐 Ubuntu 20.04 / CentOS 8放弃 cron拥抱 systemd timer。它更可靠、更易监控、且与 certbot 官方包深度集成。# 1. 确保 certbot 包是最新版避免旧版 timer bug sudo apt update sudo apt install --only-upgrade certbot python3-certbot-nginx # 或 CentOS: sudo yum update certbot python3-certbot-nginx # 2. 启用并启动 timer sudo systemctl enable --now certbot.timer # 3. 验证 timer 已激活且下次触发时间合理 sudo systemctl status certbot.timer sudo systemctl list-timers | grep certbot # 4. 可选调整 timer 触发间隔默认 12h可改为 8h 以更快发现问题 sudo systemctl edit certbot.timer # 在打开的编辑器中输入 # [Timer] # OnCalendar*-*-* 00,08,16:00 # Persistenttrue # 保存退出然后重载 sudo systemctl daemon-reload sudo systemctl restart certbot.timer为什么推荐 systemd timer因为 cron 在系统休眠、时间跳跃如 NTP 校准时可能丢失执行而 systemd timer 的Persistenttrue选项会在系统唤醒后立即补上错过的任务。这对云服务器尤其是 Spot Instance至关重要。4.2 修复验证层Nginx 配置的黄金三原则Nginx 是 ACME HTTP-01 挑战的守门员。它的配置必须满足三个硬性条件原则一80 端口必须监听且无重定向Let’s Encrypt 的验证服务器只走 HTTP非 HTTPS且不跟随重定向。如果你的 Nginx 配置中有return 301 https://$host$request_uri;它会直接返回 301挑战失败。正确做法为验证路径单独开一个server块仅监听 80 端口且不包含任何重定向。# /etc/nginx/conf.d/acme-challenge.conf server { listen 80; server_name example.com; location ^~ /.well-known/acme-challenge/ { root /var/www/html; default_type text/plain; # 禁用所有可能干扰的模块 try_files $uri 404; } # 其他 location 一律 deny确保只响应 challenge location / { return 404; } }然后sudo nginx -t sudo systemctl reload nginx。原则二.well-known路径必须精确匹配禁止正则泛匹配location ~ /\. { deny all; }这种写法会匹配/.well-known/因为.是正则特殊字符需转义。正确的写法是location ~ /\.well-known/ { deny all; }但更好的做法是显式允许location ^~ /.well-known/acme-challenge/ { # 允许且仅此路径 }^~表示前缀匹配优先级高于~正则确保挑战请求一定落到这个 location。原则三Webroot 路径权限必须开放如果使用--webroot方式/var/www/html/.well-known/acme-challenge/目录的 owner 必须是 Nginx worker 用户www-data或nginx且权限为755。否则 certbot 写入 token 文件时会 Permission Denied。sudo mkdir -p /var/www/html/.well-known/acme-challenge/ sudo chown -R www-data:www-data /var/www/html/.well-known sudo chmod -R 755 /var/www/html/.well-known4.3 修复部署层用 deploy-hook 替代 --nginx实现原子化 reload--nginx参数的缺陷在于它调用nginx -s reload但如果 reload 失败如配置语法错误certbot 不会回滚且不会通知你。更稳妥的方式是使用--deploy-hook将 reload 封装在一个可监控的脚本中。# 创建 deploy hook 脚本 sudo tee /usr/local/bin/certbot-deploy-nginx.sh EOF #!/bin/bash # 该脚本在每次 certbot renew 成功后执行 set -e # 1. 测试 Nginx 配置 /usr/sbin/nginx -t # 2. 重载 Nginx不中断服务 /usr/sbin/nginx -s reload # 3. 记录成功日志 logger -t certbot Nginx reloaded successfully for domain: $RENEWED_DOMAINS # 4. 可选发送通知 # echo Cert for $RENEWED_DOMAINS renewed at $(date) | mail -s Cert Renewal Success adminexample.com EOF sudo chmod x /usr/local/bin/certbot-deploy-nginx.sh # 修改 renewal 配置添加 deploy-hook sudo sed -i /^\[.*\]$/a deploy_hook /usr/local/bin/certbot-deploy-nginx.sh /etc/letsencrypt/renewal/example.com.conf # 验证配置 sudo certbot renew --dry-run这个脚本的关键是set -e任何一行命令失败如nginx -t报错整个脚本立即退出certbot 会标记此次 renewal 为失败并在日志中清晰记录原因。比--nginx的“静默失败”强百倍。5. 常见问题与排查技巧实录那些官方文档不会告诉你的坑5.1 “lets encrypt isrg root x1 最低支持 java jdk 内置版本是多少”——一个被严重误解的问题这个热搜词背后是大量 Java 应用开发者在升级 JDK 后遇到 HTTPS 调用失败错误日志里出现PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target。他们误以为是 Let’s Encrypt 的 ISRG Root X1 证书不被 JDK 信任于是疯狂搜索“JDK 最低版本”。真相是ISRG Root X1 证书自 2021 年 9 月起成为 Let’s Encrypt 的默认根证书它被所有主流 JDK 8u101、JDK 9、JDK 11 原生信任。问题从来不在 JDK 版本而在你的 Java 应用是否正确配置了信任库cacerts。JDK 的cacerts文件位于$JAVA_HOME/jre/lib/security/cacerts旧版或$JAVA_HOME/conf/security/cacertsJDK 9。它是一个 JKS 格式的密钥库里面预置了全球主流 CA 的根证书。ISRG Root X1 就在其中。你可以用以下命令验证# 列出 cacerts 中所有证书的别名含 ISRG $JAVA_HOME/bin/keytool -list -v -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit | grep -A1 ISRG Root X1 # 正常输出应包含Alias name: isrgrootx1, Owner: CNISRG Root X1, OUSecOps, OInternet Security Research Group, CUS真正的原因通常是你的应用指定了自定义的信任库-Djavax.net.ssl.trustStore/path/to/custom.jks而这个 custom.jks 里没有导入 ISRG Root X1或者你使用了 Bouncy Castle 等第三方安全提供者但未正确初始化或者你的服务器时间严重偏差±5 分钟导致证书的Not Before时间被 JDK 认为无效。解决方案不要升级 JDK而是检查你的应用启动参数移除自定义 trustStore或手动将 ISRG Root X1 导入到 custom.jks# 下载 ISRG Root X1 证书 wget https://letsencrypt.org/certs/isrg-root-x1.pem # 导入到自定义 keystore keytool -import -trustcacerts -file isrg-root-x1.pem -alias isrgrootx1 -keystore custom.jks -storepass yourpassword5.2 CentOS 7 certbot 兼容性问题Python 2.7 的末日倒计时CentOS 7 默认 Python 版本是 2.7而 certbot 1.0 已全面转向 Python 3。官方早已停止对 Python 2 的 certbot 支持。如果你在 CentOS 7 上执行certbot --version得到0.31.0或更低恭喜你你正运行在一个已 EOL 的 certbot 版本上。风险无法支持新的 ACME v2 协议特性无法处理 wildcard 证书的 DNS-01 挑战--nginx插件在新版 Nginx 上可能崩溃最重要的是Let’s Encrypt 已于 2023 年 10 月 1 日起停止向使用 ACME v1 协议的客户端颁发新证书。你的旧 certbot 可能连certbot renew都失败。修复方案二选一方案 A推荐升级到 certbot-auto已弃用但仍是 CentOS 7 最后选择# 下载并赋予执行权限 sudo wget https://dl.eff.org/certbot-auto sudo mv certbot-auto /usr/local/bin/certbot-auto sudo chmod ax /usr/local/bin/certbot-auto # 使用 certbot-auto它会自动管理 Python 环境 sudo /usr/local/bin/certbot-auto --nginx注意certbot-auto 项目已于 2023 年底正式归档但其二进制仍可工作至 2024 年底。这是 CentOS 7 用户的过渡方案。方案 B终极迁移到 Python 3 环境# 安装 SCLSoftware Collections仓库 sudo yum install centos-release-scl # 安装 Python 3.9 sudo yum install rh-python39 # 启用 Python 3.9 环境 scl enable rh-python39 bash # 在此环境下安装 certbot pip3 install certbot certbot-nginx # 创建 wrapper 脚本 /usr/local/bin/certbot-py39 echo #!/bin/bash | sudo tee /usr/local/bin/certbot-py39 echo source /opt/rh/rh-python39/enable | sudo tee -a /usr/local/bin/certbot-py39 echo exec /opt/rh/rh-python39/root/usr/bin/certbot $ | sudo tee -a /usr/local/bin/certbot-py39 sudo chmod x /usr/local/bin/certbot-py39 # 使用新命令 sudo certbot-py39 --nginx5.3 Nginx 配置与证书路径的“幽灵错位”一个隐藏极深的权限陷阱现象certbot renew日志显示成功ls -l看证书时间戳是新的nginx -t通过nginx -s reload无报错但openssl s_client仍显示旧证书。根源Nginx 的ssl_certificate指令指向的是/etc/letsencrypt/live/example.com/fullchain.pem这是一个软链接指向/etc/letsencrypt/archive/example.com/fullchain1.pem。而certbot renew会创建fullchain2.pem并更新软链接。但如果/etc/letsencrypt/archive/目录的 owner 不是root或fullchain2.pem的权限不是644Nginx worker 进程以www-data用户运行就无法读取新文件只能继续使用旧的fullchain1.pem缓存。排查命令# 查看 live 目录软链接目标 ls -l /etc/letsencrypt/live/example.com/fullchain.pem # 输出fullchain.pem - ../../archive/example.com/fullchain2.pem # 查看 archive 目录下文件权限 ls -l /etc/letsencrypt/archive/example.com/ # 正常应为-rw-r--r-- 1 root root ... fullchain2.pem # 查看 Nginx worker 进程的 UID/GID ps aux | grep nginx | grep -v master | head -1 | awk {print $1} # 然后检查该用户是否能读取 fullchain2.pem sudo -u www-data cat /etc/letsencrypt/archive/example.com/fullchain2.pem /dev/null 21 echo
返回列表