给站点上 HTTPS,我现在的默认方案就是 Let's Encrypt 配 certbot:证书免费,申请走命令行,续期靠定时器自动完成。对个人站、博客、小团队业务来说,这套组合几乎是最省心的选择,不需要去云控制台导出导入证书,也不会出现证书到期忘了换导致浏览器直接打不开。很多人一听“申请 SSL 证书”就觉得要填表单、等人工审核,其实用 certbot 能全程自动化。这篇我会把整套流程掰开揉碎讲一遍——ACME 验证机制、certbot 安装、首次签发、自动续期配置、常见问题排错,全部按我实际操作时常用的顺序来写,新手照着做基本一遍能跑通。
1. 为什么默认推荐 Let's Encrypt 加 certbot 组合
1.1 HTTPS 证书解决的核心问题
很多人把证书理解为“装了就安全”,其实它干的是两件事:身份证明和加密传输。
先说加密。TLS 握手时会协商出一把会话密钥,之后浏览器和服务器之间的数据都用这把密钥加密,中间人即使在网络里抓到数据包也读不出明文。但这只是加密,不能解决“对方到底是谁”的问题,于是就需要第二件事——身份证明。证书里写清楚了域名、签发机构、有效期,浏览器通过验证签发机构的信任链来确认“这个域名确实是这家机构认证过的”。
打个比方,加密相当于把信放在上锁的箱子里,证书则是箱子外面的防伪标签。没有标签的箱子虽然也能锁,但没人能确认寄件人是谁。这就是自签名证书的问题:它确实能加密,但浏览器不认识签发它的机构,所以会弹出红色警告页,用户根本不敢继续访问。
Let's Encrypt 的价值就在这里:它是一个被主流浏览器信任的免费签发机构。它签发的证书和商业 CA 的证书在用户体验上没有本质区别,浏览器打开后直接显示小锁,不需要任何额外设置。
1.2 certbot 把申请动作从手动变成自动
Let's Encrypt 解决了“免费 + 被信任”的问题,但证书申请这件事仍然有门槛。你需要按照 ACME 协议向证书机构提交订单、响应验证、下载证书、部署到 Web 服务器,每一步都有格式要求。
certbot 就是干这个的客户端。它替你完成了和证书机构之间的全部对话,还能主动修改 Nginx、Apache 的配置,自动加载新证书。更关键的是,它在申请完成后会把续期所需的信息保存在本地,之后你只需要运行certbot renew,它就会自动判断哪些证书快过期、需要续期,该续的续,不该续的跳过。
这套自动化的价值在 Let's Encrypt 的 90 天有效期政策下尤其明显。证书不是“办一次用三年”,而是每三个月就要更新一次。靠人工记日子换证书,迟早会忘。certbot 的定时器机制就是为了对付这个场景设计的。
1.3 几种常见证书方案的取舍
选型之前可以先看看市面上有哪几类方案。
| 方案 | 成本 | 浏览器信任 | 有效期 | 自动化程度 | 适合场景 |
|---|---|---|---|---|---|
| 自签名证书 | 免费 | 不信任,需手动导入 | 可自定义 | 低 | 内网测试、开发环境 |
| 云厂商免费证书 | 多数免费 | 信任 | 通常较短 | 一般,需控制台操作 | 少量业务域名,不介意手动部署 |
| Let's Encrypt | 免费 | 信任 | 90 天 | 极高,命令行+定时器 | 个人站、企业外网服务 |
| 商业证书 | 费用不等 | 信任 | 一般 1-2 年 | 取决于服务商 | 有合规、供应链审计等特殊要求 |
我见过很多人纠结要不要买商业证书,其实对绝大多数 Web 服务来说,Let's Encrypt 的信任级别已经够用。商业证书的优势主要是有效期长、有统一的技术支持,但代价是费用和一次次人工续期。
云厂商免费证书也不是不能用,但它往往要求你在控制台申请、填写域名、等待审核,然后手动下载证书包,再上传到服务器。如果你只有一两台机器还能忍受,机器多了以后,这套流程会变成纯粹的重复劳动。certbot 最大的优势恰恰是把这种重复劳动压缩成一条命令。
2. 动手前先理解 ACME 验证机制
2.1 HTTP-01:验证文件要被公网访问到
Let's Encrypt 给你签发证书之前,必须先确认你确实拥有这个域名。最常见的验证方式叫 HTTP-01 挑战,流程是这样的:
- certbot 向证书机构发起申请,机构返回一个随机 token。
- certbot 把 token 和对应的 key 写到一个临时文件里,放在
http://你的域名/.well-known/acme-challenge/路径下。 - 证书机构通过公网访问这个 URL,读到内容后和它此前生成的值比对。
- 一致,就说明你有能力在域名对应的服务器上放文件,证书机构才会继续签发。
整个过程可以理解成:机构要求你在自己家门口贴一张指定内容的纸条,它走过来看到纸条,就认定你确实是这间屋子的主人。
这个机制对部署环境有一条硬性要求:你的 80 端口必须能被公网访问。证书机构是从外部发起请求的,不是在你本机验证。很多新手在服务器上curl一下发现文件可以访问,就以为没问题,结果申请仍然失败,原因就是云服务商的安全组没放行 80 端口。
另外,如果一个 HTTP 请求被 301 重定向到 HTTPS 或其他域名,验证也容易失败。所以.well-known这个路径尽量不要做跳转、不要做 CDN 缓存,让它原样返回。
2.2 DNS-01:通配符证书和复杂网络环境的首选
HTTP-01 只能验证单个域名,而且必须能访问到对应服务器的 80 端口。如果你需要通配符证书,也就是一张证书同时覆盖example.com和*.example.com,HTTP-01 就无能为力了,这时候得用 DNS-01 挑战。
DNS-01 的流程是:证书机构要求你在域名的 DNS 记录里添加一条_acme-challenge.example.com的 TXT 记录,记录里的值是一个随机字符串。它通过查询 DNS 来确认你对该域名的控制权。因为能改 DNS 的人,基本等于拥有整个域名。
certbot 支持在验证时通过域名服务商提供的 API 自动添加和删除这条 TXT 记录。只要你在申请命令里指定对应的 DNS 插件,它会全自动完成,不需要你手动进控制台点鼠标。这样续期也能保持全自动。
DNS-01 的优点是:不依赖 80 端口、不受服务器位置限制,甚至可以签发用于纯 CDN 或负载均衡环境的证书。缺点是需要你提供 DNS 服务商的 API 凭证,这等同于把域名的部分操作权限交给了 certbot,所以凭证权限最好限制在只允许修改 TXT 记录,不要给全量账号权限。
如果你只是给一台普通服务器上的普通域名配 HTTPS,优先用 HTTP-01 就够了,没必要为一两个域名去碰 DNS-01。但如果你有通配符需求,或者服务器处在 80 端口不可用的网络环境里,DNS-01 是绕不开的。
2.3 验证方式与插件的匹配关系
有时候你会看到--nginx、--webroot、--standalone这些词,它们不是新的验证方式,而是 certbot 落地验证文件的手段。
--nginx:certbot 自动找到你的 Nginx 配置,自动生成验证文件,签发完后还能顺手帮你把 HTTPS 配置写进去。最省事。--webroot:指定网站根目录,certbot 把验证文件写到该目录下,适用于 Apache、OpenResty 等无法被插件直接管理的 Web 服务。--standalone:certbot 临时占用 80 或 443 端口自己响应验证。适合当前没有 Web 服务在运行的场景,但如果 Nginx 正在监听 80 端口,会冲突,需要先停服务。
在我实际使用的经验里,只要机器上跑的是 Nginx,就无脑选--nginx插件。它把“签发”和“配置生效”两件事合并成一件事,后续续期时也会自动处理 reload,少踩很多坑。
3. 证书申请与自动续期:逐步实操
3.1 开始前的四项检查
不要急着装 certbot,先花两分钟检查环境,很多申请失败都是下面这些前置条件没满足。
第一,域名解析。确认example.com和www.example.com都指向你准备配 HTTPS 的这台服务器。证书机构验证时会按域名访问,如果解析错了机器,后面全白费。可以用dig +short example.com检查。
第二,端口放行。HTTP-01 验证需要 80 端口被公网访问;DNS-01 不需要 80,但需要 DNS 插件权限。如果你走 HTTP-01,先确认云服务商的安全组、服务器防火墙、SELinux 都没有拦 80。
第三,服务器时间。证书相关的加密协议对时间敏感。用date看一眼系统时间,如果偏差超过几分钟,后续签名验证很可能出问题,建议配置 NTP 时间同步。
第四,软件环境。新一点的 Debian/Ubuntu 系统基本都带 Python 3 和对应依赖,apt 安装 certbot 时会把需要的包一并装上。如果发现装不了,先apt update。
3.2 certbot 的三种安装方式
certbot 的安装方式会直接决定后续自动续期任务的形态,选对了能省非常多事。
最常见的源码包方式需要使用 Python 包管理工具安装,优点是版本新,但缺点是要自己处理依赖、路径和自动续期任务。比较推荐给容器场景使用。
Debian/Ubuntu 用户可以直接用 apt 安装:
sudo apt update sudo apt install -y certbot python3-certbot-nginxapt 版本的好处是随系统维护,稳定性有保障,通常会自动带上 cron 续期任务。
我自己的主力服务器用的是官方推荐的 snap 方式:
sudo snap install --classic certbot sudo ln -s /snap/bin/certbot /usr/bin/certbotsnap 版本自带所有插件,包括 Nginx 插件和各大 DNS 服务商插件,后续升级也方便。它安装后会自动注册一个 systemd timer 用于自动续期。如果你用的是某些精简系统,snap 可能没启用,需要先安装 snapd。
三种方式里,我个人建议新手要么用 apt、要么用 snap。自己用 Python 包管理工具装容易把环境搞乱,而且续期任务得手写,没必要。
3.3 一条命令用 Nginx 插件完成签发
假设你的域名是example.com,Nginx 已经在你服务器上跑起来,并且已经为这个域名配了 server block,那么签发命令很简单:
sudo certbot --nginx -d example.com -d www.example.com --email admin@example.com --agree-tos --redirect参数含义:
--nginx:告诉 certbot 使用 Nginx 插件,自动解析配置。-d:指定需要签发证书的域名。多个域名放在同一条命令里,证书会同时涵盖这些域名。--email:用于接收到期提醒,建议用真实可接收邮件的地址。--agree-tos:同意证书机构的服务条款。--redirect:签发成功后自动把 HTTP 请求 301 到 HTTPS。
命令执行成功后,证书会保存在/etc/letsencrypt/live/example.com/目录下。你不需要手动改 Nginx 配置,插件已经帮忙写进去了。
如果你不想让 certbot 自动改 Nginx 配置,也可以去掉--nginx,改用:
sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com这种模式适合 Web 服务不是 Nginx 管理的场景。
3.4 配置自动续期:renew、timer 与钩子
证书申请成功只是开始,真正决定长期体验的是自动续期。
Let's Encrypt 的证书有效期是 90 天,certbot 的设计是:当证书剩余天数不足 30 天时才发起续期。所以不要跑到手动执行certbot renew,看到“No renewals were attempted”就以为出问题了,这是正常现象。
snap 版 certbot 会自带 systemd timer,每天执行两次续期检查。检查是否启用:
sudo systemctl status certbot.timer不同安装方式、不同发行版下,timer 名称可能是certbot.timer或snap.certbot.renew.timer,只要systemctl list-timers里能看到对应条目即可。如果没启用,可以手动开启:
sudo systemctl enable --now certbot.timerapt 版通常会装一个 cron 任务,路径在/etc/cron.d/certbot,同样会每天运行续期检查。
续期成功之后,Nginx 需要重新加载才能读取新证书。如果你用的是--nginx插件,certbot 在续期过程中通常会自动完成这一步。但如果当初用的是certonly --webroot,或者你的 Web 服务不是插件能直接管理的,就需要配置一个钩子,让 certbot 在拿到新证书后触发重载:
sudo certbot renew --quiet --deploy-hook "systemctl reload nginx"--deploy-hook只会在证书真正发生续期后执行,不会每次检查都触发。
配置完之后,最重要的一步是测试:
sudo certbot renew --dry-run这条命令会模拟完整的续期流程,但不会真的替换证书。它用的是证书机构的测试环境,不会消耗你正式签发的配额。
3.5 多站点与多域名场景怎么组织
一台服务器上挂多个站点很常见,组织方式直接影响续期复杂度。
如果几个域名业务关联强,比如主站和它的 www、移动端子域名,放在同一张证书里是合理的:
sudo certbot --nginx -d site1.com -d www.site1.com -d api.site1.com如果域名之间业务独立,我建议分别申请证书。好处是一旦某个域名的验证配置出问题,不会拖累其他站点的续期。
注意不要为了省事把大量无关域名塞进一张证书,因为 Let's Encrypt 对同一组合的重复申请有频率限制。证书到期前用 renew 正常续期没关系,但如果反复删除重建,或者测试环境不停签发,很容易触动限制,导致“证书请求过于频繁”类的错误。
4. 实操过程记录:从签发到续期验证
4.1 首次签发输出里要看懂哪些关键行
我用一个模拟环境跑一遍,展示一下重点输出长什么样。
$ sudo certbot --nginx -d example.com -d www.example.com --email admin@example.com --agree-tos --redirect Saving debug log to /var/log/letsencrypt/letsencrypt.log Requesting a certificate for example.com and www.example.com Performing the following challenges: http-01 challenge for example.com http-01 challenge for www.example.com Waiting for verification... Cleaning up challenges Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem Key is saved at: /etc/letsencrypt/live/example.com/privkey.pem Deploying certificate to VirtualHost /etc/nginx/sites-enabled/example.com.conf Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-enabled/example.com.conf你只需要关注几个关键点:
Performing the following challenges表示验证开始,看到http-01说明走的是 80 端口验证。Waiting for verification表示证书机构正在回访,这一步可能持续几十秒。Successfully received certificate表示签发成功。Certificate is saved at后面的路径就是证书位置。Deploying certificate表示 Nginx 配置已被自动修改。
如果最后一步没出现,说明插件没有成功修改 Nginx 配置,需要手动把证书路径配到 SSL 配置里。
4.2 dry-run 和 force-renewal:测试续期别用错
自动续期配置完之后,立刻可以做一次干跑测试:
sudo certbot renew --dry-run我看到很多新手把这条命令理解成“手动续期一次”,其实它不是。dry-run 是用测试环境完整走一遍续期逻辑,包括验证流程、证书下载、钩子执行,但结果不写入正式目录,也不会替换你已经存在的证书。它的价值是提前暴露配置错误和网络问题,不影响正式配额。
真正强制续期的命令是:
sudo certbot renew --force-renewal这条会无条件重新申请一张正式证书,并替换当前证书。它适合的场景很窄,比如你知道证书私钥可能泄漏了,需要立刻轮换。日常不要用,因为会消耗签发配额,反复操作还可能触发频率限制。
判断续期是否成功,最直接的依据是 exit code。命令执行完看返回值:
sudo certbot renew --dry-run echo $?返回 0 表示成功,非 0 需要看具体的报错信息。
4.3 用 openssl 和浏览器双重验证
证书配好之后,不要只看浏览器小锁,服务器侧也要验证。
查看本地证书到期时间:
sudo openssl x509 -enddate -noout -in /etc/letsencrypt/live/example.com/fullchain.pem输出类似:
notAfter=May 12 12:00:00 2026 GMT从外部视角检查证书是否已正确部署:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates如果返回的日期和服务器上的一致,说明 HTTPS 服务确实在用新证书。
浏览器侧的验证更简单:访问https://example.com,点击地址栏锁图标,查看证书签发机构和有效期。出现 Let's Encrypt 和对应日期就说明部署成功。
5. 常见问题与排查技巧实录
5.1 验证失败:八成出在解析、端口或跳转
申请证书时报错最集中的区域就是验证环节,比如:
Challenge failed for domain example.comhttp-01 challenge for example.com后一直不成功
优先排查三件事。
第一,域名解析。确认域名真的指向当前这台服务器,并且你申请时写的域名没有拼错。常见的坑是只申请了example.com,但用户访问的是www.example.com,浏览器就会报证书域名不匹配。
第二,80 端口。证书机构从外网访问你的域名时,必须能连到 80 端口。如果 Nginx 正在监听 80,但云安全组没放行,验证仍然会失败。可以用一个在线端口检测工具或者从另一台机器curl -I http://example.com/.well-known/acme-challenge/测试路径验证。
第三,HTTP 跳转。如果 Nginx 配置里对所有 HTTP 请求都强制 301 到 HTTPS,证书机构的验证请求也会被重定向,导致它拿不到验证文件。解决方案是给.well-known路径单独放行,不要让它参与全局跳转。
5.2 证书文件与 Web 服务的权限和配置
证书安装好之后,Nginx 启动报错,或者浏览器显示证书链不完整,这类问题通常出在权限和文件使用上。
默认情况下/etc/letsencrypt/live/example.com/下的私钥只有 root 可读,Nginx 主进程以 root 启动时没有问题。如果你自定义了运行用户,需要确保那个用户能读取live目录下的私钥文件。不要简单粗暴地chmod 644 privkey.pem,这会扩大私钥暴露面,更稳妥的是调整所属组,或者用 ACL 给特定用户授权。
另一个常见问题是配置文件里使用了错误的文件:
ssl_certificate应该用fullchain.pem,这是服务器证书和中间证书的合体。ssl_certificate_key用privkey.pem。- 有人图省事直接写
cert.pem,结果缺少中间证书,部分浏览器会报“证书链不完整”。
如果访问时出现重定向循环,多半是配置写成了“所有 HTTP 都跳 HTTPS”,但证书验证路径没有放行。把location ^~ /.well-known/acme-challenge/单独配置为不重定向即可解决。
5.3 续期逻辑失效或没生效怎么办
自动续期不是配好 timer 就万事大吉,我见过不少“证书明明过期了,系统也没报错”的案例,原因基本都是下面几种。
第一种,timer 根本没启用。用systemctl list-timers查看,如果没有任何 certbot 条目,手动执行systemctl enable --now或检查对应 cron 是否存在。
第二种,续期成功了,但服务没重载。证书文件在硬盘上已经替换成新的,Nginx 进程却还在用内存里的旧证书。解决方法是配--deploy-hook "systemctl reload nginx",让续期成功后自动重载。
第三种,手动跑certbot renew时看到 “No renewals were attempted”。这不是故障,是因为证书还有超过 30 天才过期,certbot 的策略是不到窗口期不动作。想验证流程就用--dry-run。
第四种,检查日志。所有申请和续期记录都会写入/var/log/letsencrypt/letsencrypt.log,排错时先看这里,能直接看到失败原因。
5.4 问题定位速查表
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
| 验证失败,提示 challenge failed | 80 端口不通、解析错误、HTTP 跳转 | 检查公网端口、dig解析,放行.well-known路径 |
| 浏览器提示证书链不完整 | 配置用了cert.pem而非fullchain.pem | 改ssl_certificate |
| 证书域名不匹配 | 申请时漏掉 www,或后续新增子域名 | 重新用多个-d申请或补一张证书 |
| 证书到期未更新 | timer 未启用、cron 不存在 | systemctl enable --now certbot.timer |
| 续期成功但访问仍旧证书 | Nginx 没 reload | 配置--deploy-hook "systemctl reload nginx" |
| 私钥权限导致启动失败 | Web 服务用户读不到私钥 | 调整组权限或使用 ACL,不要 644 裸奔 |
| 提示请求过于频繁 | 重复删除重建、测试环境占用正式配额 | 使用 staging 测试,等待配额恢复 |
| 服务器时间不对导致签发失败 | 时钟漂移 | 运行 NTP 同步并检查date |
6. 进阶建议:把证书运维做成长治久安
6.1 用 certbot certificates 管好所有证书
机器上证书多了以后,别靠记忆,直接用命令查看:
sudo certbot certificates它会列出每张证书涵盖的域名、证书路径、私钥路径、到期时间,以及对应的续期配置文件位置。续期配置在/etc/letsencrypt/renewal/下,每个证书一个 conf 文件,里面包含钩子、验证方式等关键参数。我建议每次部署新证书后都扫一眼这个输出,确认域名列表没有覆盖错。
如果某个站点下线了,对应的证书也不会自动清理。可以用:
sudo certbot delete --cert-name example.com删除前先想清楚,这个证书如果部署在其他机器上,删掉会影响那台机器的续期。
6.2 加一层监控和告警兜底
自动续期再可靠,也扛不住一些意外情况,比如服务器连续关机、网络故障、证书机构临时回滚。我习惯在 Web 服务之外再加一层独立监控,让证书剩余天数跌破阈值时能主动通知。
最简单的脚本思路是读取证书到期时间并计算剩余天数:
expire=$(openssl x509 -enddate -noout -in /etc/letsencrypt/live/example.com/fullchain.pem | cut -d= -f2) days=$(( ( $(date -d "$expire" +%s) - $(date +%s) ) / 86400 )) echo "证书剩余: ${days} 天"把这条命令放进定时任务,当days小于 15 天时发一条通知,你就拥有了一道人肉兜底。如果你们团队有现成的监控告警平台,也支持“证书有效期”类型的检查项,直接配到站点监控里更省事。
6.3 容器化与 CI 流程里的证书思路
如果你的业务已经容器化,用 certbot 的思路要稍微变一下。
不要在构建镜像的时候申请证书。镜像构建是一次性的,证书文件和续期状态不会持久化,下次重新构建又要重新申请,对配额和安全性都不友好。更合理的做法是让证书文件存在于宿主机或共享存储卷里,Web 容器通过挂载读取;续期任务由宿主机上的 timer 或专门的 certbot 容器负责,续期后触发入口容器 reload。
如果入口层用的是自带 ACME 能力的反向代理方案,它自己就能管理证书生命周期,不需要单独装 certbot,但理解 TLS 验证机制仍然是排查问题的底层能力。
最后分享一个我自己的操作习惯:不管装哪台的证书,配完以后的固定动作是先跑一遍certbot renew --dry-run,再确认对应 timer 处于 active 状态,然后让服务重载一次。这套检查大概花不到一分钟,但能提前暴露权限、路径、钩子绝大多数问题。证书体系是个长期工程,出了问题往往不是某一个时刻的配置,而是运维习惯里少了某个固定检查项。把验证做进部署清单,比到期前手忙脚乱要省心太多。