news 2026/10/10 8:10:35

CentOS 7.9下certbot自动续期SSL证书:从安装到nginx完整接入指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CentOS 7.9下certbot自动续期SSL证书:从安装到nginx完整接入指南

1. 为什么选择certbot:证书过期恐惧症的终极解药

先交代一下背景。我手头维护着几台CentOS服务器,上面跑着nginx、Tomcat之类的服务,前几年一直用阿里云的免费证书。阿里云免费证书本身没什么毛病,一年申请一次,但痛点在于:你得记着续期这件事。每年到时间了,登录控制台,重新申请、下载、上传服务器、改nginx配置、reload。这套流程走下来,运气好十分钟,赶上控制台改版或者证书类型变更,折腾半小时也不是没可能。

更麻烦的是多台服务器的情况。我有个客户,三台服务器,每台域名还不一样,每年到了续期月就得挨个处理。有一次我出差在外,其中一台服务器证书到期没来得及换,结果客户那边小程序直接白屏,因为接口全是https的。那次之后我就决定,必须上certbot这种自动续期的方案。

certbot是Let's Encrypt官方推荐的客户端工具,作用是自动申请、部署、续期SSL证书。它跟传统证书体系最大的区别在于:证书有效期只有90天,但通过定时任务可以做到全自动续期,一旦配置好,理论上这辈子不用再手动碰证书。用生活类比的话,普通证书像是一年一换的纸质年检标,certbot像是ETC,走一次流程,后面自动扣费、自动更新,你只管开车就行。

这篇文章我会把整个流程拆开讲:从环境准备、安装、签发证书,到nginx接入、自动续期,再到几个容易踩的坑,比如离线安装、nginx替换证书不生效这些实际工作中一定会遇到的问题。适合刚接触Linux运维的人,也适合已经用nginx有一段时间但证书管理还靠手动的老手。

2. 安装前的三个关键判断:域名、解析、端口

2.1 域名解析必须提前搞定

certbot签发证书时,Let's Encrypt服务器会尝试访问你的域名来验证你对该域名的控制权。验证方式主要有两种:HTTP-01(访问域名下的特定路径)和DNS-01(在DNS记录里加TXT记录)。默认情况下certbot用的是HTTP-01验证。

这意味着什么?你的域名必须已经正确解析到这台服务器上。我见过不少人卡在这一步:域名还在旧服务器指向,或者DNS记录刚改还没生效,然后跑certbot报错Failed to connect to domain for verification,就开始怀疑是防火墙的问题、是certbot的问题,其实根本原因是解析都没通。

检查解析很简单:

dig +short example.com curl -I http://example.com/.well-known/acme-challenge/test

第二条命令是模拟Let's Encrypt的验证请求。如果服务器上没配nginx站点,可能会返回404,但只要能连上、有HTTP响应,就说明网络链路通了。如果连404都没有,先排查解析和防火墙。

2.2 80端口必须暴露

HTTP-01验证需要Let's Encrypt服务器通过80端口访问你的服务器。很多服务器为了安全默认只开了443和22,80端口是关的,这会导致验证失败。

前面提到的热词里有“gpt网络配置问题ssl证书”,其实不少人遇到的所谓“网络配置问题”,排查到最后就是80端口没放行。在阿里云这类云平台上,除了服务器本身要开放端口,安全组规则里也要放行,这两层是独立的,任何一层没开都连不上。

检查端口是否监听:

ss -lnt | grep :80

如果nginx还没装上或者站点没配置,这一条可能看不到输出,这是正常的,因为端口还没被占用。但安全组和系统防火墙层面必须允许入站80。

2.3 certbot运行方式的选择

certbot支持两种主要的签发方式,这个选择会影响后面的安装配置:

  • nginx插件模式(--nginx):certbot自动修改你的nginx配置,注入证书路径和443监听,最省事,但要求nginx是标准安装且配置结构规范。
  • webroot模式(--webroot -w /path/to/webroot):certbot在指定目录下创建验证文件,由已经运行的nginx提供服务,不修改nginx配置,适合配置比较复杂、不想让工具乱动的场景。

我个人的建议是:第一次用certbot,别用nginx插件模式。原因后面会详说,但简单讲就是它改配置的方式有时候跟你的预期不一致,改完了你还要去检查、可能还要改回来,反而多一道工序。用webroot模式,收发全在自己掌控里。

3. CentOS 7.9安装certbot:在线与离线两条路

3.1 在线安装:EPEL源是唯一正路

CentOS 7默认的yum源里没有certbot,必须启用EPEL(Extra Packages for Enterprise Linux)源。

yum install -y epel-release yum install -y certbot

装完验证一下:

certbot --version

这里有个老生常谈但值得再提的坑:如果你的服务器在国内,EPEL源下载可能很慢,甚至超时。解决方案是换成国内的镜像源,比如阿里云镜像。方法:

mv /etc/yum.repos.d/epel.repo /etc/yum.repos.d/epel.repo.bak curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo yum clean all && yum makecache

切换之后速度会有质的提升。另外注意,certbot在EPEL里是一个大而全的包,依赖挺多的,安装过程拉几十个依赖包是正常的,别看到一堆依赖就怀疑卡住了。

3.2 离线安装:在一台能联网的机器上做搬运工

热词里特别提到了“centos7.9 离线安装certbot”,这个场景在政企内网、专网环境非常常见。思路其实一句话:找一台同系统版本的、能联网的机器,把rpm包都下载下来,拷贝到内网机器上本地安装。

具体步骤:

第一步,联网机器上配置好EPEL源,然后:

mkdir /opt/certbot-rpms yum install --downloadonly --downloaddir=/opt/certbot-rpms certbot

这条命令会下载certbot以及所有依赖的rpm包,但不会安装。下载完检查一下目录:

ls /opt/certbot-rpms

正常会有几十个rpm,包括python2-certbot、python2-acme、python2-mock等等。注意这些包的依赖链条很长,缺一个装不上,所以全部拷过去最稳妥。

第二步,把整个目录打包传进内网机器:

tar czvf certbot-rpms.tar.gz /opt/certbot-rpms

第三步,在内网机器上进入目录,直接:

cd /opt/certbot-rpms rpm -Uvh *.rpm

rpm会自动按照依赖顺序安装。如果提示依赖缺失,大概率是漏拷了package,回联网机器重新执行第二步的下载命令补齐即可。

第四步,离线环境下certbot运行还需要处理证书更新时的网络可达性问题。内网环境如果无法访问Let's Encrypt的服务器,离线安装certbot的意义主要在签发本机构建的自签证书或私有CA签发的证书,或者通过内网ACME服务器签发。如果是纯内网场景,还需要配置certbot的ACME目录地址:

certbot --server https://your-internal-acme-server/directory

这个选项官方文档有提到,但实际用的人少,这里单独拿出来讲是因为离线环境的核心瓶颈就在网络层。

3.3 为什么不直接推荐snap安装

搜certbot安装教程,会看到不少文章推荐用snap安装,官方文档也把snap作为首选方案。但在CentOS 7.9上,我实测snap安装的certbot是python3版本,运行起来内存占用更高,而且snap本身在CentOS 7上需要额外装epel源里的snapd,两三层套娃,折腾的时间不如直接用yum源里的python2版本。Python 2版本的certbot虽然官方已停止新功能开发,但核心的签发、续期功能完全够用,稳定得很。

4. 签发证书与nginx接入的完整演示范例

4.1 webroot模式签发:先建好验证目录

假设我的域名是blog.example.com,网站根目录在/var/www/blog。

先建一个ACME验证用的目录:

mkdir -p /var/www/blog/.well-known/acme-challenge

然后执行签发:

certbot certonly --webroot -w /var/www/blog -d blog.example.com

第一次运行时certbot会问你两个问题:一个是邮箱地址(用于过期提醒),一个是同意服务条款。邮箱建议填真实邮箱,证书快过期时Let's Encrypt会发提醒邮件,万一自动续期哪天失效了,你还有时间手动补救。

签发成功后,证书文件在/etc/letsencrypt/live/blog.example.com/目录下,四个文件:

  • cert.pem:域名证书本身
  • chain.pem:中间证书链
  • fullchain.pem:cert.pem和chain.pem合并,nginx里配这个
  • privkey.pem:证书私钥

4.2 nginx配置的关键细节:证书文件别用错

nginx的ssl配置长这样:

server { listen 443 ssl; server_name blog.example.com; ssl_certificate /etc/letsencrypt/live/blog.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/blog.example.com/privkey.pem; # 其他配置... }

这里有个很多人第一次用certbot容易踩的细节:ssl_certificate必须用fullchain.pem,不是cert.pem。如果你只用cert.pem,浏览器会报错提示“服务器证书链不完整”,因为中间证书没有合进去。

另外,/etc/letsencrypt/live/blog.example.com/这个路径其实是个软链接,指向../../archive/blog.example.com/下的具体版本文件。这个机制的好处是每次续期后live目录下的文件名不变,nginx配置不用改。但坏处是如果你不小心删了archive目录,live下的链接就断了,证书直接失效。

配置完nginx,测试一下语法然后reload:

nginx -t systemctl reload nginx

4.3 80端口跳转443:顺手做掉

用Let's Encrypt证书的站点,一般都会顺带把http访问强制跳到https。这里有个容易忽略的点:如果80端口没做跳转、网站又能正常通过http访问,搜索引擎会认为你有两个重复内容站点,影响SEO。

跳转配置:

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

注意:这个配置和webroot验证不冲突。certbot已经完成验证后,.well-known/acme-challenge路径其实可以不用保留。但建议不要删,因为自动续期默认走同一种验证方式,下次续期还要用到这个路径。如果你把80的跳转声明放在最前面,且恰好有location /.well-known/acme-challenge的优先匹配,就不会有冲突问题。

5. nginx替换SSL证书不生效:完整的排查链路

这个场景我想单独拉出来好好讲讲,因为热词里明晃晃写着“nginx替换ssl证书不生效”,而且这个坑我踩过不止一次。

5.1 现象描述

某天你拿到了新证书,替换了fullchain.pem和privkey.pem,执行了nginx -s reload,浏览器里一看,证书还是旧的。或者更诡异:电脑上Chrome显示新证书,手机上Safari显示旧证书。

5.2 原因一:nginx reload时节流导致worker进程还占用旧证书

nginx reload并不是所有worker进程立刻退出。它会启动新配置的新worker进程,同时让旧worker进程处理完手头的连接再退出。**在旧连接还没处理完时,旧worker仍然使用旧证书。**最典型的就是HTTP/2长连接,浏览器和服务器之间一个连接挂很久,旧worker一直不退,新的连接进来走了新worker、新证书,而旧连接还占着老证书。

排查方法:

ps -ef | grep nginx

看有没有多个worker进程的启动时间不一致。如果确实有老进程,可以用:

nginx -s reload sleep 3 nginx -s reload

连续reload两次,把上一次的worker进程全部换掉。更粗暴的办法是nginx -s stop再nginx,直接重启整个服务,不过这会造成瞬间断连,生产环境慎用。

5.3 原因二:证书文件路径被软链接误导

/etc/letsencrypt/live/目录是软链接这个事前面提过。如果你手动替换文件时,直接vi编辑了live目录下的cert.pem,其实你改的是软链接指向的那个目标。假设你vi保存时把软链接的三元结构(cert.pem、privkey.pem、chain.pem)其中之一变成了普通文件或者删掉重建,live目录的链接结构就乱了。

遇到过最典型的场景:有人为了图方便,把新证书直接复制到/etc/letsencrypt/live/blog.example.com/cert.pem,覆盖了软链接本身。结果原链接指向的archive目录还在,而live下变成了一个独立文件。下次续期时,certbot默认去写archive目录,但nginx读的还是那个值,两边各写各的,证书彻底分叉。

这种问题的排查方法:

ls -l /etc/letsencrypt/live/blog.example.com/

如果输出里没有->符号,说明软链接被干掉了。修复办法:重新建立链接关系,或者干脆删掉这个目录让certbot重新生成。我建议清理掉重新签,干净利落。

5.4 原因三:客户端缓存骗过了你的眼睛

这也是一个容易让人白忙活半天的坑。很多浏览器对证书有本地缓存,尤其是HSTS(HTTP Strict Transport Security)开启后,浏览器强制走HTTPS且会缓存证书状态。你这边证书其实已经换成功了,但浏览器还是显示旧证书。

排查时不要只看浏览器,用命令行工具验证最可靠:

openssl s_client -connect blog.example.com:443 -servername blog.example.com 2>/dev/null | openssl x509 -noout -dates

这个命令直接跟服务器握手,拿到的是真实证书的生效时间、过期时间,不经过浏览器缓存。如果这个输出已经显示新证书,那问题就在浏览器缓存,强制刷新(Ctrl+F5)或者换个无痕窗口验证即可。

5.5 原因四:nginx的ssl证书配置被include覆盖

有人说“我明明改了nginx conf,为什么reload后还是旧证书”。一种情况是同一个server块被多次定义,nginx默认取最后一个匹配的。另一种情况是配置里用了include动态加载,比如ssl_certificate在别处的文件中被再次赋值。

排查手段:

nginx -T

这条命令会把nginx最终生效的完整配置输出出来。直接搜ssl_certificate,看到底生效的是哪个文件、哪一行。如果报错提示“unknown directive”,通常是配置文件语法问题,也能一并暴露。

5.6 一个验证的方案:把这串脚本固化下来

为了以后不再重复排查,我自己把检查步骤写成了一个shell脚本,每次换完证书就跑一遍:

#!/bin/bash DOMAIN=blog.example.com echo "== 证书有效期 ==" openssl s_client -connect ${DOMAIN}:443 -servername ${DOMAIN} 2>/dev/null | openssl x509 -noout -dates echo "== live目录软链接 ==" ls -l /etc/letsencrypt/live/${DOMAIN}/ echo "== nginx生效配置 ==" nginx -T 2>/dev/null | grep -A2 ssl_certificate

输出一目了然,到底是浏览器缓存、nginx worker没换、还是配置指向错了,十秒内定位。

6. 自动续期配置与阿里云免费证书的取舍分析

6.1 dry-run和定时任务

certbot续期命令本身:

certbot renew --dry-run

--dry-run是演练模式,会完整走一遍续期流程,但不真正替换证书。第一次配置完建议先跑一遍dry-run,确认流程通畅。

确认没问题后,添加定时任务。certbot装好后默认会在/etc/cron.d/certbot里生成一条定时任务,内容大致是每天执行两次:

0 */12 * * * root test -x /usr/bin/certbot && /usr/bin/certbot renew -q --post-hook "systemctl reload nginx"

这条任务默认在,但有个前提:nginx的reload这个post-hook不一定在里面。旧版本的certbot自动生成的cron可能只有certbot renew -q,没有reload动作。证书文件虽然更新了,但nginx没重载,等于白更新。检查一下你的cron文件内容,没有就补上。

crontab -l

我的习惯是单独写一条自己的cron,不依赖certbot生成的:

15 3 * * * /usr/bin/certbot renew -q --renew-hook "systemctl reload nginx"

注意我把时间设置在凌晨3点15分,避开Let's Encrypt服务器的高峰时段,也避免白天用户在访问时突然reload。--renew-hook跟--post-hook的区别:renew-hook只在证书确实续期成功之后执行,而post-hook每次跑完都执行,哪怕这次没续期。用renew-hook更精准,减少无意义的nginx reload。

6.2 续期失败的常见原因:80端口被占、DNS解析漂移

自动续期并不是100%成功,尤其是运行久了之后。最常见的失败原因:

  • 80端口被其他服务占用:比如你后来在服务器上挂了别的Web服务,占用了80,导致ACME验证无法通过。
  • 域名解析变了:比如把站点从这台服务器迁到了另一台,但certbot还在这台跑,验证自然失败。
  • 防火墙规则变更:安全组调整时顺手封了80,但443还开着,用户访问正常,唯独续期悄悄失败。

我的建议是:每月手动看一眼续期日志。日志位置在/var/log/letsencrypt/letsencrypt.log,月底翻一翻,看看有没有renewal failed字样。虽然证书到期前Let's Encrypt会发邮件提醒,但邮件万一进了垃圾箱呢。

6.3 阿里云免费证书 vs Let's Encrypt:一张表看清差异

热词里“阿里云ssl证书免费续期”出现频率很高,我索性把两者的对比写透。阿里云免费证书额度也够用,一年一签,但它的自动化程度和certbot根本不在一个量级。用一张表总结:

对比项阿里云免费证书Let's Encrypt证书(certbot管理)
有效期通常3个月或1年(政策变动频繁)90天
签发方式控制台申请,手动部署命令行自动,无需登录网页
自动续期不支持,到期需重新申请支持,cron定时任务自动续
部署工作量每年一次约15分钟首次配置30分钟,之后零维护
多服务器管理每台单独申请配置同一定时任务可管多域名多服务器
支持域名数量一般单域名或仅一级域名单域名、多域名、泛域名通吃
失败恢复难度手动下载更换失败可随时再来自动续期失败需查日志定位,有一定门槛

如果只是个人博客、测试站点,我强烈建议直接上certbot。如果是企业业务、对证书的品牌和信任链有特殊要求(比如某些App接口强制要求OV证书),那阿里云或其它云厂商的证书仍然必要,但那些证书的部分到期需要人工确认,没有绕过的办法。

6.4 手动迁移:已有一张证书,怎么转到certbot统一管理

有读者可能会问:“我现在用的是阿里云证书,想换成certbot,但又不敢贸然迁,怕出问题。”这个迁移有个稳妥的流程:

  1. 先在服务器上装好certbot,用webroot模式签发新证书,不要动nginx的配置。
  2. 确认签发新证书成功,然后在nginx里把ssl_certificate和ssl_certificate_key指向/etc/letsencrypt/live/目录。
  3. nginx -t && systemctl reload nginx,这时候切换就完成了,且整个过程没有证书空窗期。
  4. 把阿里云那张旧证书解绑删除(先别急着删,过一周看没什么问题再说)。

这个流程的核心思路:新老证书并存,切换只是改配置,随时可以回滚。

7. 兜底经验:那些教程不会告诉你的certbot真相

7.1 live目录下的软链接是“花架子”,别有依赖

不少教程会让你直接在nginx配置里写死/etc/letsencrypt/live/domain/fullchain.pem,然后说“续期后无需修改配置”。这句话听起来很美,但有一个前提:整个/etc/letsencrypt目录结构不能被破坏。

我碰到过一次:某运维为了清理磁盘,把/etc/letsencrypt/archive目录整个删了,以为证书都下载过不需要了。结果第二天所有https站点直接挂了,因为nginx读的软链接指向的文件不存在了。certbot证书文件虽然不大,但archive目录里存的是历史上所有版本的证书,每个证书文件不到5KB,一个域名十年累积也不到200KB,真没必要动它。

7.2 证书私钥文件权限必须收紧

certbot签发的privkey.pem默认权限是600,属主是root。这个默认值是对的,千万别手欠改成644。如果私钥文件允许其他用户读取,等于是把服务器大门钥匙挂在了门口。

如果你在配置时遇到nginx无法启动、报错提示“cannot load certificate key”,检查一下权限:

ls -l /etc/letsencrypt/live/blog.example.com/privkey.pem chmod 600 /etc/letsencrypt/live/blog.example.com/privkey.pem

7.3 证书过期后更换,浏览器中间的“信任断裂期”怎么避免

Let's Encrypt证书有效期90天,自动续期通常会在到期前30天开始尝试。但如果你中途做了服务器迁移、域名更换,自动续期链条断了,证书过期了你才知道,此时站点已经无法访问。这时候再跑certbot certonly --webroot -w /var/www/blog -d blog.example.com --force-renewal强制续签,能救回来,但中间那段时间用户访问就是报错状态。

避免这种尴尬的最笨但最有效的方法:给“证书到期前第14天”设一个日历提醒,定期打开/var/log/letsencrypt/letsencrypt.log扫一眼。真正接管certbot之后,每年花在证书上的精力基本就是这几眼日志的时间。

8. 一次真实踩坑复盘:certbot续期成功但网站还是打不开

写到最后,分享一次我实际遇到的诡异问题,希望能帮读者省去一次深夜加班。

某客户的站点证书过期了,按常规操作跑到服务器上续期,日志显示:

Congratulations, all renewals succeeded: /etc/letsencrypt/live/blog.example.com/fullchain.pem

续期成功,nginx也reload了,但客户的浏览器还是显示证书过期。我用openssl命令验证:

openssl s_client -connect blog.example.com:443 -servername blog.example.com 2>/dev/null | openssl x509 -noout -dates notBefore=Jan 1 00:00:00 2025 GMT notAfter=Jan 1 00:00:00 2026 GMT

证书确实是旧的。这就很奇怪了,renewals succeeded却还是旧证书。最后排查到原因:这台服务器上装了多个nginx实例,一个在宿主机上,一个在Docker容器里。certbot续期没问题,post-hook reload的宿主机的nginx也没问题,但客户访问的域名实际透过宿主机反代到Docker里的另一个nginx,而Docker里的nginx没有被reload,还在用旧证书。

这个案例想说的不是Docker的问题,而是续期链条的最后一环——reload动作,必须覆盖实际对外提供服务的那个进程。如果你用了任何形式的反向代理、负载均衡、Docker映射,都要确认证书续期后的reload动作能穿透到最终提供TLS终结的那个节点。否则任何一环断了,外面看起来“证书过期”,但certbot日志里一切都正常。

检查方法不复杂:续期后主动访问一下站点,用命令行确认证书日期,别只看certbot的renew日志。眼见为实,这句老话在运维领域是铁律。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 8:10:11

可编程PMIC实现多路电源管理:PCA9422与PIC32MX695F512L实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/10 8:09:02

开源掌机5W全解析:从硬件原理、演进简史到入坑避坑指南

最近我在整理柜子时翻出一台老开源掌机,装上电池居然还能开机。旁边就是手机,里面随便装个模拟器都能跑同样的游戏,可我还是愿意花半小时把那台小掌机充满电、调好音量、再插上耳机。这问题我想过很久:手机明明更方便,…

作者头像 李华
网站建设 2026/10/10 8:08:50

PCA9422+PIC32MX实现可监控可配置嵌入式电源管理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/10 8:08:46

PCA9422与STM32F107VC协同实现嵌入式电源管理设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/10 8:04:57

世界技能大赛网络安全赛项A模块:系统加固实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/10 8:03:51

Claude Code配置实战:从安装到高效使用的完整指南

第一次接触 Claude Code 是在一个普通的工作日下午。当时手头有个多模块项目要改,网页对话窗口来回复制粘贴代码,改到第七八轮的时候我已经分不清哪个版本是新、哪个是旧,更别提让模型持续理解整个仓库的结构了。有人跟我说,试试命…

作者头像 李华