2026/10/15 7:46:45

Let‘s Encrypt免费证书与certbot自动化部署HTTPS实战指南

Let‘s Encrypt免费证书与certbot自动化部署HTTPS实战指南 1. 我为什么最终换成Lets Encrypt免费证书前几年给个人站点部署HTTPS时我一直觉得SSL证书是该花钱的东西。去云厂商买一年期证书域名多的话动辄几百上千还得每年走一遍申请、验证、下载、部署的流程。更尴尬的是云厂商提供的某些免费一年证书其实在不少浏览器里已经不受信任了用户访问时照样弹出安全警告等于白装。后来换到Lets Encrypt certbot这套方案之后最大的感受是证书这件事终于从买了再忘变成配好就忘。Lets Encrypt是全球性的免费证书颁发机构它的特点是证书有效期只有90天但支持自动化续期配合certbot这个客户端工具可以实现从申请、部署到续期的全流程无人值守。很多人一听90天续期就觉得麻烦实际上恰恰是短有效期逼迫整个流程自动化反而比一年期手动换证书省心得多。这篇文章就围绕我实际部署的过程来写覆盖几个核心问题验证方式怎么选、初始证书怎么签发、Nginx落地配置、自动续期链路怎么搭以及我在使用中踩过的具体坑。适合手里有域名和服务器、想让站点跑上HTTPS但还没动手的开发者也适合已经装了证书但续期一直靠手动、想改造为自动化的运维朋友。需要先说明一点下面所有操作都以Nginx为例操作系统以Debian系为主但提到的原理和排查思路在CentOS等RHEL系上同样适用命令上稍微换一下包管理器即可。2. 免费证书的两张王牌Lets Encrypt和certbot分别干什么很多人会把Lets Encrypt和certbot混为一谈其实它们是两个不同角色的东西。搞清楚分工后面排查问题时思路会清晰很多。2.1 Lets Encrypt是签发方certbot是申请工具Lets Encrypt本质上是一个证书颁发机构也就是CACertificate Authority。你可以把它理解为发证机关它的职责是验证你确实拥有某个域名然后签发出对应的SSL证书。所有浏览器都信任它的根证书所以用它签发的证书在浏览器里能正常显示小锁图标。certbot则是一个运行在你的服务器上的客户端程序。它负责跟Lets Encrypt的服务器通信完成验证域名所有权和获取证书这两个动作并且还能把获取到的证书自动安装到Web服务器的配置里。整个通信过程走的是一套叫ACME的开放协议certbot是这套协议最流行的客户端实现之一。2.2 为什么证书有效期要压到90天这是Lets Encrypt最具争议也最核心的设计。传统商业证书有效期通常是一年甚至两年长期不换带来的问题是一旦私钥泄露或者域名控制权易主错误会被长期保留而且CA机构很难推动所有人及时整改。90天有效期的逻辑是把续期变成常态。证书快到期就自动换一张相当于每三个月做一次安全刷新。配合ACME协议续期这个动作完全可以由程序完成不需要人工介入。对于运维人员来说这意味着只要第一次配置正确后面基本不用再管证书的事。2.3 certbot安装在服务器上天然适合自动化certbot以命令行工具的形式运行支持webroot、standalone、DNS验证等好几种验证插件也支持Nginx等Web服务器的自动配置插件。它的续期逻辑很简单certbot renew会扫描所有已签发的证书对离过期不足30天的证书执行续期对还不需要续期的证书直接跳过。这个设计保证了定时任务每次跑都是幂等的不会重复签发。这里有个小知识并不是所有CA机构都支持这种完全自动化的签发流程。有些提供免费证书的平台虽然也免费但要走网页验证、人工审核甚至只能手动下载证书文件这对自动化部署来说非常不友好。Lets Encrypt certbot的优势在于ACME协议天然支持机器对机器交互任何环节都不需要人工点击。3. 动手前的准备域名、端口、安装方式三件事在这套方案里最容易翻车的地方不在certbot本身而在环境准备。我见过不少新手卡在验证不通过上多半是服务器防火墙没有放行端口或者Web服务已经把验证请求劫持走了。3.1 域名解析先确认证书签发的第一步是验证你对域名的控制权所以域名必须已经做好解析并且服务器能响应这个域名的访问。这里说的解析不只是能ping通而是HTTP请求能成功到达你的Nginx。建议在开始之前先在服务器上执行一下curl -I http://你的域名如果能看到HTTP响应头哪怕是404也算说明域名已经指向这台服务器。如果连不通先去检查DNS解析记录和云服务商的安全组规则这一步没搞定后面certbot再怎么折腾都是白费。3.2 80端口是验证的生命线Lets Encrypt证书签发有一个非常硬的条件对应域名从公网上必须能访问80端口。为什么必须是80而不是443因为证书还没签发时443端口上通常还没有可用的证书所以验证只能走HTTP明文流量。不管你是开发环境还是生产环境都要保证公网IP的80端口不回绝请求尤其不能把80端口直接封死只开放443。很多人在防火墙规则里顺手把80关掉结果申请证书时一直报验证超时。排查的时候可以先curl -v http://你的域名/.well-known/acme-challenge/测试试试确认这个路径能正常访问再跑certbot。3.3 certbot安装方式怎么选certbot的安装方式在Linux上有好几种我实际用下来简单做个对比安装方式优点缺点适用场景系统包管理器apt/yum安装简单自带systemd定时器版本可能略旧大多数Linux用户推荐snap商店版本官方推荐更新及时依赖snapd启动路径特殊Ubuntu等支持snap的系统Docker容器隔离干净不影响宿主环境文件卷映射稍复杂续期也要管容器容器化部署环境certbot源码运行完全可控需要自己处理依赖和升级极少数特殊需求我个人的经验是能用系统包管理器就用系统包管理器。因为包管理器在安装certbot的时候通常会把配套的cron任务或systemd定时器一并装好省去很多手工配置。用snap版本的话会自动启用snap的定时任务逻辑上也OK但如果你对snap的隔离机制不熟排查问题时会多一层障碍。安装时也可以顺手把Nginx插件装上这样certbot可以直接读取Nginx配置并自动修改省得自己手动编辑配置文件。具体安装命令就不写了因为不同系统差异比较大搜索你的系统 certbot安装就能找到对应教程。注意别漏掉插件包比如python3-certbot-nginx这类。4. 证书签发的两种验证方式HTTP-01和DNS-01怎么做选择证书签发的核心动作是证明域名是你的。ACME协议提供好几种证明方式实际部署时最常用的是HTTP-01和DNS-01这两种我都在生产环境里用过各自的开销和适用场景很不一样。4.1 HTTP-01验证适合单域名和自动续期HTTP-01的流程是这样的certbot向Lets Encrypt服务器请求验证后会在你的服务器上生成一个随机token文件放到网站根目录下的/.well-known/acme-challenge/路径里然后Lets Encrypt的服务器会从公网请求这个文件能访问到且内容匹配就说明你确实控制着这个域名的Web服务。这种方式的优点是配置简单、续期完全自动。只要你的Nginx一直在跑80端口可访问续期时certbot自动放置新token和完成验证全程不需要人工操作。但HTTP-01也有一个硬限制它只能验证80端口能访问到的域名。如果你域名后面挂的是负载均衡、CDN或者是你不想在80端口暴露任何服务这条路就走不通了。我用webroot插件来实施HTTP-01验证核心命令大概是certbot certonly --webroot -w /var/www/html -d www.example.com -d example.com这里的-w指定网站根目录-d指定要签发的域名。certbot会把验证文件放在/var/www/html/.well-known/acme-challenge/下面Nginx需要保证这个路径能正常被外部访问到。4.2 DNS-01验证能签发泛域名但续期更麻烦DNS-01验证的原理是要求你在域名的DNS解析记录里添加一条TXT记录记录值内容由Lets Encrypt指定。你添加完成后Lets Encrypt通过查询这条TXT记录来确认域名控制权。这种方式的优点是能签泛域名证书也就是一张证书覆盖*.example.com这样的所有子域名。HTTP-01做不到这点因为泛域名的每一个子域名都可能指向不同的服务器。但DNS-01的缺点是续期自动化更难。如果DNS解析服务商没有提供API你就得每次续期时手动去解析面板添加TXT记录这违背了自动化的初衷。即使支持API也得额外配置认证凭据涉及凭据存储安全复杂度提升了一个量级。所以我的建议很简单默认优先用HTTP-01只有明确需要泛域名证书时才考虑DNS-01。大多数个人站点有几个二级域名用多域名单证书完全够没必要为泛域名给自己找麻烦。5. Nginx下完整的证书签发和配置流程下面这一步一步的流程是我在几台服务器上都验证过的照着走基本不会出问题。5.1 第一步确认Nginx正常运行并放行80端口检查Nginx状态systemctl status nginx如果没装先装好并启动。然后确认80端口在监听ss -tlnp | grep :80这一步千万不能省略我就在这翻过车当时80端口被一个临时服务占用我以为Nginx没启动排了半天才发现问题。5.2 第二步Nginx配置需要有一个可访问的验证路径为了让HTTP-01验证能成功Nginx配置里最好显式加上对验证路径的放行规则。在server块中添加下面的内容location ^~ /.well-known/acme-challenge/ { root /var/www/html; default_type text/plain; }这里有一个常见坑如果你的Nginx配置里有重定向规则、防盗链规则或者错误处理规则可能把/.well-known/路径拦截掉。比如有些人会写一条所有未知路径都rewrite到首页这条规则必须放在上面这块验证路径配置之后否则Lets Encrypt请求验证文件时会被重定向走导致验证失败。如果你用certbot --nginx插件模式它会在签发时临时修改Nginx配置来保证验证路径可用签发完再帮你恢复正常配置。这种方式省心很多但要求certbot对Nginx配置有写权限。5.3 第三步执行证书签发命令以webroot方式为例完整的签发命令是certbot certonly --webroot -w /var/www/html -d www.example.com -d example.com执行过程中certbot会确认几个事情邮件地址、是否同意服务条款、是否共享统计数据。按提示输入即可。签发成功后会看到类似这样的输出Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/www.example.com/fullchain.pem Key is saved at: /etc/letsencrypt/live/www.example.com/privkey.pem看到这个输出说明证书已经拿到手了。建议顺手查看一下证书信息certbot certificates这条命令会把服务器上所有通过certbot签发的证书列出来包括域名、到期时间、证书路径。我每次配置完都要跑一遍这个命令确认信息无误。5.4 第四步理解证书文件目录的含义很多新手拿到证书路径后一头雾水不知道每个文件是干什么用的。/etc/letsencrypt/live/下的每个域名目录里有四个关键文件文件作用Nginx配置项cert.pem服务器证书本体ssl_certificatechain.pem证书链中间证书ssl_trusted_certificatefullchain.pem证书 完整证书链最常用ssl_certificateprivkey.pem私钥必须严格保密ssl_certificate_key实际配置Nginx时绝大多数情况下只需要用到fullchain.pem和privkey.pem。千万别在Nginx配置里只填 cert.pem那样浏览器会报证书链不完整的错误。我见过有人为了省事把三个证书文件内容拼在一起填进去的其实fullchain.pem已经做了这个事没必要重复。5.5 第五步修改Nginx配置启用HTTPS在Nginx的server块里加上监听443端口和证书路径的配置server { listen 443 ssl http2; server_name www.example.com; ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem; root /var/www/html; # 其余站点配置... }改完之后测试配置并重载nginx -t nginx -s reloadnginx -t是测试配置文件语法表示测试通过才执行reload。这是安全习惯防止配置写坏了直接导致Nginx挂掉。我还会再加上一段把80端口的HTTP请求重定向到HTTPS让用户访问老链接时也能自动跳到加密连接server { listen 80; server_name www.example.com; return 301 https://$host$request_uri; }注意这里有个隐患如果你80端口重定向到HTTPS那么Lets Encrypt的验证请求也会被重定向走。所以重定向规则必须排除/.well-known/路径或者在certbot续期验证时使用Nginx插件让它临时处理。这也是为什么很多老手愿意在80端口的server块里保留验证路径的location规则。下面是一段安全的80端口配置模板server { listen 80; server_name www.example.com; location ^~ /.well-known/acme-challenge/ { root /var/www/html; default_type text/plain; } location / { return 301 https://$host$request_uri; } }这样验证请求不会被重定向而正常访问仍然跳HTTPS。5.6 检查证书是否生效配置完成后用浏览器访问一下自己的域名地址栏应该出现小锁。也可以在服务器上通过命令行检查curl -vI https://www.example.com看输出里有没有SSL certificate verify ok的信息。如果有说明证书链完整配置没问题。6. 自动续期timer、cron和deploy-hook的完整配置证书签发只是第一步真正体现自动化价值的在于续期环节。Lets Encrypt证书只有90天有效期如果续期没做好三个月后网站就会恢复正常访问时弹出证书错误。所以这一章是整个方案的重中之重。6.1 续期机制是怎么工作的certbot的续期检查命令是certbot renew --dry-run手动执行时certbot会检查所有证书是否在30天内到期如果不需要续期就显示not yet due for renewal之类提示直接跳过。如果确实在30天窗口内才会执行续期操作。这个提前30天才续期的设计很聪明相当于给证书留出了足够的缓冲时间即使定时任务因为服务器宕机、网络故障等意外原因连续几天没执行只要故障时间不超过一个月都还能在证书过期前补上。真遇上超过一个月的长时间故障那服务器本身可能也没在正常运行了。也正因这一点续期任务根本不需要频繁执行每天跑一次完全足够。我甚至见过有人每小时跑一次续期任务的纯属自己吓自己白白增加CA服务器的负担。6.2 两种定时方案systemd timer还是crontabcertbot在Debian系系统上安装时会自动生成一个systemd服务包括certbot.service和certbot.timer。这意味着只要安装正常系统的定时器就已经在运行了你不需要额外配置cron。可以用下面的命令确认定时器状态systemctl status certbot.timer如果输出显示activewaiting说明定时续期已经就位。如果你用的是CentOS或者其他没有自动安装定时器的环境可以手工添加crontab0 3 * * * /usr/bin/certbot renew --quiet --deploy-hook systemctl reload nginx每天凌晨3点检查一次--quiet表示只在出错时输出信息--deploy-hook表示续期成功后执行Nginx重载。我个人更偏向systemd timer而不是crontimer自带日志、可以通过systemctl list-timers直观查看下次执行时间、还能设置随机延迟避免整点扎堆。另外certbot官方文档也对timer方式更推荐。6.3 为什么用deploy-hook而不直接reload Nginx续期成功只是把新证书文件写到磁盘上但Nginx如果继续用旧的证书进程提供服务那新证书不会生效。所以必须让Nginx重新加载配置。这里有两个思路一是直接修改renew命令加上reload动作二是用deploy-hook。区别在于renew命令每次执行都会触发reload即使没有续期而deploy-hook只在证书确实被更新时触发。显然deploy-hook更精准避免不必要的服务重载。需要注意要用nginx -s reload而不是nginx -s restart。reload是平滑重载不中断现有连接restart会短暂中断服务对在线业务有感知。有些教程写restart这是非常不负责任的做法我在生产环境里特意测过reload完全能让新证书生效不需要restart。6.4 验证续期链路畅通配置完自动续期后一定要手动执行一次干跑验证certbot renew --dry-run干跑模式会真实地向Lets Encrypt发起续期请求、真实执行验证但最后会把写证书的动作伪装掉不会真的替换现有证书。所以它是检验整个续期链路是否正常的金标准。如果干跑失败说明自动续期配置有问题宁可在现在排查清楚也不要等到三个月后证书真过期了才发现。我自己的习惯是每次改完服务器配置、迁移服务器、调整Nginx规则之后都会重新执行一次干跑确认续期没有受影响。毕竟续期链路涉及的环节不少任何一环断了问题都会潜伏到证书快过期时才爆发。7. 我踩过的坑和排查思路从验证404到期权问题这一章写几个我在实际使用中真正遇到过的故障每一个都花了不少时间排查。列出来是希望大家能直接跳过。7.1 坑一验证路径返回404签发失败最初在一台新服务器上部署时我用webroot模式签发证书报错内容是类似Failed to connect to domain或者404 Not Found。当时我的第一反应是Nginx没配好但检查后Nginx运行正常80端口也通。后来逐层排查发现问题出在Nginx的server块里有一段catch-all规则把所有未匹配的路径都rewrite到了首页。而webroot生成的验证文件明明已经写到网站根目录但请求到达Nginx时被rewrite规则拦截掉了返回的是首页内容验证自然失败。解决办法就是我在前面提到过的在server块中显式添加验证路径的location规则并保证它优先匹配。排查的时候可以用下面的命令模拟curl -v http://www.example.com/.well-known/acme-challenge/x如果返回404或者重定向而不是正常返回token内容说明规则有问题。7.2 坑二80端口被防火墙挡掉验证请求进不来还有一次是在云服务器上申请证书怎么弄都提示验证超时。curl从服务器本机去访问80端口是通的但从外网访问就是不响应这基本可以确定是云安全组或者系统防火墙的问题。检查iptables和firewalld状态再检查云服务商控制台的安全组规则。最后发现安全组只放行了22和443端口80端口没有开放。这种问题很隐蔽因为自己本机访问不受影响只有从外部进来的流量才被拦截。一定要记得签发的HTTP-01验证是Lets Encrypt服务器从外网发起请求而不是你的服务器自己验证自己。所以本机能访问不代表外网能访问端口和防火墙的通达性必须以公网视角来确认。7.3 坑三证书文件权限问题导致Nginx无法读取证书签发完成后Nginx reload时报权限拒绝Permission denied。原因是certbot生成的文件默认权限是0600或者属于root用户而Nginx的worker进程是以www-data或nginx用户运行的读不了这些文件。解决方式有两种一是直接修改目录下文件的权限和所有者为Nginx用户二是使用Nginx的ssl_certificate配置时带上完整的读取权限。其实certbot默认会在/etc/letsencrypt/live/目录组里设置合适的权限但如果你中途用chmod命令改过导致权限混乱就得手动修复chown root:www-data /etc/letsencrypt/live/你的域名/*.pem chmod 640 /etc/letsencrypt/live/你的域名/*.pem不过这里要注意不要把私钥文件权限放开成644那意味着服务器上任何用户都能读取私钥。私钥泄密等于加密失效宁可报错也不能放宽权限。7.4 坑四证书到期前30天续期窗口被忽略还有一次是到期前一天才想起来看发现证书还有一天就过期了立刻执行certbot renew却提示不需要续期。这是因为certbot只在到期前30天内续期而我之前实际上已经有过一次失败的续期尝试把续期窗口从还有30天推到了更晚。其实真正的问题是我当时把服务器时间调过导致certbot判断证书剩余时间和真实情况不符。解决办法是先把系统时间校准date timedatectl set-ntp true然后重新执行certbot renew --force-renewal这条命令会不管窗口期、强制重新签发证书。这条命令在生产环境慎用但在救急时非常有效。7.5 坑五reload vs restart带来的短暂断连之前有一次我图省事直接把Nginx restart了结果监控告警提示有短暂的服务不可用。对普通个人博客可能无所谓但如果是面向用户的线上服务几十毫秒的断连也会变成一张工单。从那以后我的所有deploy-hook一律使用reload。这里再强调一次reload足够让新证书生效不需要restart。如果你发现reload后证书没更新多半是Nginx配置里的证书路径不对而不是reload动作不够彻底。8. 多域名证书、泛域名和证书管理的一些建议到这里基础的单域名HTTPS部署和自动续期就已经全部完成了。如果你域名比较多或者想进一步优化证书管理可以参考一下我后续在多个站点上总结出来的经验。8.1 多域名共用一张证书certbot允许在一条命令里签发多个域名的证书也就是SAN证书。例如certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com -d blog.example.com签发出来的是一张包含这三个域名的证书Nginx里只需要填一份证书文件就能同时服务这三个域名。优点是管理简单续期也只需要一次操作缺点是证书文件里暴露了所有域名列表而且如果其中一个域名验证失败整张证书都签不下来。对于个人站点来说我倾向于把主域名和常用子域名放在同一张证书里毕竟域名数量不多管理的复杂度能降就降。8.2 什么时候才值得用泛域名证书如果你真的需要*.example.com这样的泛域名解析HTTP-01没法验证只能用DNS-01。而DNS-01的问题在于续期时需要在DNS服务商那里操作TXT记录。如果你的DNS服务商提供API而且你愿意配置ACME DNS插件那么泛域名证书的续期也可以自动化。但这一步涉及把DNS API的凭据放在服务器上有一定安全风险。我在自己的服务器上评估过后决定还是老老实实用多域名单证书因为我的子域名是可枚举的用SAN证书完全够用。泛域名证书还有一个容易忽略的坑它并不覆盖裸域名。也就是说*.example.com并不能用来给example.com本身做HTTPS你必须把裸域名也加进去。8.3 证书到期状态怎么持续观察即使自动化续期已经安排好了也建议日常监控证书到期时间。一个简单的办法是用cron定期检查证书剩余天数并输出告警或者直接通过一些在线监控服务绑定证书到期提醒。我自己写了一个简单的检查命令放到每天的定时任务里openssl x509 -enddate -noout -in /etc/letsencrypt/live/www.example.com/cert.pem输出类似notAfterJan 1 00:00:00 2026 GMT这样的到期时间。结合脚本解析日期、对比当前时间可以在证书只剩7天时收到提醒邮件。这个脚本网上有很多现成模板就不贴完整代码了核心思路就是定期看证书有效期防患于未然。8.4 关于证书轮换和管理的一些习惯我的建议是新证书签发路径下的旧证书备份文件renewal历史版本不用频繁清理磁盘占用可以忽略。如果哪天需要回滚配置历史文件反而派得上用场。但也别让这些备份文件无限堆积每隔一段时间手动清理一下没问题。另外如果你的服务器上跑着多个不同的Web服务比如Nginx和Apache共存要注意certbot默认的管理范围是按/etc/letsencrypt目录来区分的。在签发时指定了webroot续期时就会沿用同样的配置不会因为服务环境变化而产生混乱。在实践中我还有一个习惯每次改动服务器配置前先执行一次certbot renew --dry-run拍摄一下当前续期链路是好的的快照改完配置后再执行一次对比。如果改完配置后干跑开始报错说明改动影响了验证链路可以立即回滚不至于把问题留到三个月后。9. 最后想分享的一点体会从最早手动买证书、上传证书、到期前人工续期到现在Lets Encrypt certbot一套流程自动化跑下来最大的变化不是省了多少钱而是心理负担没了。以前每隔一段时间就惦记证书是不是快过期了现在定时器在后台默默工作只要配置不坏就不再需要花精力去管它。这种配置一次、安心一整年的体验正是自动化该有的价值。如果你正在犹豫要不要从付费证书切换到Lets Encrypt的方案我的建议是直接试。流程跑通一遍之后你会发现证书这个东西真正该有的样子就是免费、自动、无感知。唯一需要克服的就是免费的东西会不会不靠谱这个心理门槛但Lets Encrypt的服务已经在全球支撑了数以亿计域名的HTTPS加密这本身就说明问题了。去申请一张自己的证书吧整个过程只要几分钟用完你也会觉得当初为什么没早点换。