这几年帮团队维护Linux服务器,最让我头疼的不是服务突然挂掉,而是HTTPS证书悄悄过期。浏览器一打开就是大红叉,客户电话比监控告警来得还快,关键是证书这东西平时压根不会有人盯着。后来把所有站点都迁移到Let's Encrypt + Certbot这套组合上,90天有效期配上自动续期,才算真正省下心。最近在Ubuntu 22.04上重新部署了好几台环境,也帮朋友处理过几次证书申请问题,正好把Certbot申请SSL证书的三种主流方式整理成一篇完整的实操笔记:Webroot、Standalone、DNS Challenge。每种方式都有明确的适用场景和对应的坑,命令和配置我都给了可以直接抄的版本,希望能帮到正在折腾HTTPS的人。如果你有Linux服务器操作经验、部署过Nginx或Apache、正准备把站点从HTTP升级到HTTPS,这篇内容就是为你准备的。
1. 环境准备与基础概念
1.1 Ubuntu 22.04下安装Certbot的推荐方式
Ubuntu 22.04的apt源里确实自带certbot,但我强烈不建议直接用apt安装。原因很简单:apt源里的certbot版本普遍偏低,而且和Let's Encrypt服务端的兼容性有时跟不上,更重要的是apt版配置自动续期比较繁琐,需要手动加cron任务,出问题排查起来也麻烦。官方现在推荐的安装方式是Snap。
Snap是Ubuntu内置的包管理方式,优点是隔离性好、自动更新,Certbot官方团队会直接维护Snap仓库,版本永远是最新的。在Ubuntu 22.04上安装步骤很简单:
bash复制sudo apt update
sudo apt install snapd
sudo snap install certbot --classic
注意命令里有个--classic参数,很多人第一次装的时候会好奇为什么不加也能装。这是因为Certbot需要访问/etc/letsencrypt等系统目录,而Snap默认的沙箱隔离机制会限制这种访问。加上--classic之后,相当于给了Certbot完整权限,这样才能正常读写证书文件。
装完之后你会发现certbot命令还不能直接用,因为Snap的安装路径是/snap/bin/certbot,不在系统的PATH环境变量里。需要手动做一个软链接:
bash复制sudo ln -s /snap/bin/certbot /usr/bin/certbot
然后验证一下版本:
bash复制certbot --version
如果你之前用apt装过certbot,先卸载干净再走Snap流程:
bash复制sudo apt remove certbot
这一点务必记住,我遇到过一台服务器上同时存在两个certbot的情况,命令执行时调用的版本不同,行为差异很大,排查起来非常折磨。
1.2 三种认证方式的核心区别与选型逻辑
Certbot申请证书的核心原理是证实你对域名的控制权。Let's Encrypt作为证书颁发机构,不会随便给一个域名签发证书,它必须要验证你确实拥有这个域名。Certbot提供了三种主流的验证方式,每种方式的验证路径不同,适用场景也完全不同。
| 认证方式 | 验证原理 | 依赖条件 | 典型场景 |
|---|---|---|---|
| Webroot | 通过HTTP访问服务器指定目录下的验证文件,确认你对服务器有控制权 | 已有Web服务运行在80端口,且能修改站点配置 | 已有Nginx/Apache在跑,不希望中断服务 |
| Standalone | Certbot临时启动一个Web服务器来响应验证请求 | 80端口(或其他指定端口)空闲可用 | 新服务器还没配置Web服务,或可以短暂停服 |
| DNS Challenge | 在域名的DNS记录中添加指定的TXT记录,确认你对域名解析有控制权 | 能登录DNS管理后台,或配置DNS自动插件 | 需要通配符证书、内网服务器、80端口不可公网访问 |
选型逻辑很简单。你的服务器上已经有Nginx占着80端口,而且网站正在正常工作,那就选Webroot。服务器刚买回来,什么都没装,80端口干干净净,选Standalone最直接。如果需要申请通配符证书*.example.com,或者服务器没有公网80端口,那就必须走DNS Challenge。这个选择很关键,选错了轻则申请失败,重则要停服务,影响线上业务。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Webroot模式:不中断Web服务申请证书
2.1 Webroot的运作原理与场景限制
Webroot模式应该是生产环境用得最多的一种方式。它利用的是HTTP-01验证机制:Let's Encrypt的服务器会尝试访问http://你的域名/.well-known/acme-challenge/随机文件名,如果这个URL能返回一个特定内容的文本文件,CA就认为你对这个域名有控制权。Certbot在Webroot模式下做的事情,就是把这个验证文件写到Web服务器能访问到的目录里,让Nginx或Apache自动响应验证请求。
这个方案最大的优势就俩字:省心。不用停服、不占额外端口、续期的时候也不用关掉Web服务,整个流程对线上业务零影响。我现在维护的所有公网站点,只要80端口能正常访问,一律用Webroot。
当然它也有限制。首先80端口必须能被公网访问到,如果服务器在NAT后面、防火墙挡了80端口,或者域名解析到了CDN只能走HTTPS回源,那HTTP-01验证就会失败。其次是必须能修改Web服务器配置,如果你用的是虚拟主机托管服务,没法改Nginx配置,这条路也走不通。
2.2 Nginx侧配置:为验证请求开一个通道
使用Webroot模式前,先在Nginx里配置好验证目录。我习惯建一个单独的目录专门放Certbot的验证文件,比如/var/www/certbot,这样不会跟业务目录混在一起。
bash复制sudo mkdir -p /var/www/certbot
然后在Nginx的80端口server块里面加上这段配置:
nginx复制server {
listen 80;
server_name example.com www.example.com;
# Let's Encrypt 验证目录
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type text/plain;
}
}
这里有个细节很多人会忽略:location前缀我用了^~,意思是匹配到以/.well-known/acme-challenge/开头的请求后,直接采用这个location块,不再继续匹配正则表达式。这个写法可以防止其他location规则干扰验证请求。另外default_type text/plain也值得加上,虽然大多数情况下不加也能工作,但有些版本的Nginx对没有扩展名的文件返回的Content-Type不对,可能导致CA解析验证文件失败。
配置好之后重载Nginx:
bash复制sudo nginx -t
sudo systemctl reload nginx
一定要先nginx -t测试配置,语法错误直接reload会导致Nginx重启失败,线上业务全挂,这种低级事故我见过不止一次。
2.3 申请命令与证书校验
Webroot模式的申请命令如下:
bash复制sudo certbot certonly --webroot -w /var/www/certbot -d example.com -d www.example.com --email admin@example.com --agree-tos --no-eff-email
各参数含义拆解一下:
certonly:只获取证书,不自动修改Nginx配置。我一直推荐这种方式,因为Certbot自动修改配置的行为有时候不够智能,可能覆盖你原有的SSL配置,自己控制更稳妥。--webroot:指定验证方式。-w /var/www/certbot:指定验证文件存放的目录,必须与Nginx配置里的location的root路径一致。-d example.com -d www.example.com:要申请证书的域名,可以写多个。需要注意,如果域名解析到了不同服务器,证书会申请失败,因为验证请求只会发到其中一台服务器上。--email admin@example.com:用于接收证书过期提醒,强烈建议填真实邮箱。Let's Encrypt会在证书快要过期时发提醒邮件,这是一个兜底的保险。--agree-tos --no-eff-email:同意服务条款,不接收EFF基金会的营销邮件。
执行后如果看到类似Successfully received certificate的输出,证书就申请成功了。生成的证书文件存放在:
code复制/etc/letsencrypt/live/example.com/fullchain.pem
/etc/letsencrypt/live/example.com/privkey.pem
fullchain.pem是包含站点证书和中间证书的完整证书链,privkey.pem是私钥文件。这两个文件的路径后面配置Nginx时都要用到。
2.4 在Nginx中挂载证书并验证HTTPS
证书申请成功后,配置Nginx的443端口SSL站点:
nginx复制server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
# 其他站点配置...
}
这里有一个非常关键的点:ssl_certificate一定要指向fullchain.pem而不是cert.pem。如果只配置cert.pem,浏览器会提示证书链不完整,因为中间证书没有随站点证书一起发送给客户端,导致验证失败。
配置完成后测试并重载Nginx:
bash复制sudo nginx -t
sudo systemctl reload nginx
然后验证一下HTTPS是否正常工作:
bash复制curl -I https://example.com
看到HTTP/2 200之类的响应就说明HTTPS已经生效。用浏览器访问时注意看一下地址栏,有小锁图标就说明证书链完整、信任关系正确。
还有一个容易踩的坑:/etc/letsencrypt/live/下面的证书文件其实是指向../../archive/目录的软链接。Certbot续期时会自动更新archive目录下的文件,软链接本身不变。所以千万不要自作聪明去手动复制证书文件到其他目录,更不要试图直接修改软链接,否则续期后Nginx可能加载的还是旧证书。
3. Standalone模式:临时占用端口换取最快验证
3.1 Standalone模式原理与适用场景
Standalone模式在三种方式中理解起来最直白:Certbot在申请证书的时候,会自己起一个临时的Web服务器,监听80端口,响应Let's Encrypt的HTTP-01验证请求。验证完成后,这个临时Web服务器自动关闭,80端口释放。
正因为它要临时占用80端口,所以适用场景也比较明确:
- 服务器上还没装任何Web服务,80端口是空闲的。
- 服务器已经装了Nginx或Apache,但可以接受短暂停服,比如深夜维护窗口或者申请测试证书。
- 需要用certbot申请证书,但不想让certbot自动修改Web服务器配置,只想把证书文件拿到手,再自己配到其他服务上(比如邮件服务器、反向代理)。
我之前在一台邮件服务器上申请过证书,那台机没有Nginx也懒得装,直接用Standalone模式就很干净。如果你的服务器上已经有Web服务在跑,用Standalone就有点折腾了,每次续期都要先停服务再申请,一不小心就中断线上业务,这种场景还是用Webroot更合适。
3.2 申请命令与端口冲突处理
最基本的Standalone申请命令:
bash复制sudo certbot certonly --standalone -d mail.example.com --email admin@example.com --agree-tos
命令里不需要指定--webroot -w那一套,只要--standalone告诉Certbot用内置的临时Web服务器就行。
执行过程中如果80端口已经被占用,Certbot会直接报错,提示你端口不可用。这时候有两个处理方案。
方案一:停掉占用80端口的服务,申请完再启动:
bash复制sudo systemctl stop nginx
sudo certbot certonly --standalone -d example.com -d www.example.com --preferred-challenges http
sudo systemctl start nginx
方案二:换一个端口,使用--http-01-port参数:
bash复制sudo certbot certonly --standalone --http-01-port 8080 -d example.com
第二种方案有个前提条件:8080端口必须能从公网访问到。如果服务器有防火墙(比如云服务器的安全组规则),要提前放行。另外,CA服务器只会访问80和443这两个默认端口?不对,实际上Let's Encrypt的HTTP-01验证会遵循响应中的重定向,它会在80端口发请求,如果Nginx做了转发,也可以落到8080。但Certbot的--http-01-port参数本质上是让Certbot临时监听在指定端口,CA服务器访问的仍然是http://域名/.well-known/acme-challenge/xxx,它会通过DNS解析拿到你服务器IP,然后去请求80端口。除非你的Nginx把80端口的验证请求反向代理到8080,否则指定端口没意义。
这里补充一个重要认知:Let's Encrypt的HTTP-01验证一定会请求80端口,它不会去请求随机端口。所以--http-01-port并不是用来逃避防火墙的,而是配合Web服务器做端口转发用的。如果80端口被防火墙挡住,正确做法是放行80端口,而不是换端口。这也是很多新手容易踩的坑,申请失败一直以为是端口占用,结果其实是安全组没放行80。
3.3 用Standalone获取证书后再挂到Web服务的完整流程
Standalone模式的最佳实践流程是:停服务 → 申请 → 启服务 → 配置证书。
bash复制# 1. 停掉占用80端口的Web服务
sudo systemctl stop nginx
# 2. 申请证书
sudo certbot certonly --standalone -d example.com -d www.example.com --preferred-challenges http --email admin@example.com --agree-tos
# 3. 启动Web服务
sudo systemctl start nginx
# 4. 配置443端口SSL,指向证书文件
# 编辑Nginx配置,添加 ssl_certificate 和 ssl_certificate_key
# 5. 验证并重载
sudo nginx -t
sudo systemctl reload nginx
流程不复杂,但有一个细节值得注意:每次续期都需要重复停服、申请、启服的流程。如果你所在团队有严格的运维规范,停服需要变更审批,那Standalone模式显然不适合长期使用。这也是我把它定义为“一次性申请工具”的原因——适合救急、适合裸服务器,不适合长期依赖。
4. DNS Challenge模式:通配符证书与无80端口场景的解决方案
4.1 为什么需要DNS Challenge
前面两种方式都有一个前提:服务器必须能被公网通过80端口访问到。但在真实业务里,这个前提经常不成立。比如:
- 服务器在企业内网,没有公网IP,或者公网IP上80端口被安全策略限制。
- 网站流量走CDN或负载均衡,CA服务器访问
http://域名/.well-known/...时,请求进了CDN节点,回源链路复杂,验证文件不一定能正确返回。 - 需要申请通配符证书
*.example.com,用一个证书保护该主域下的所有子域。通配符证书只支持DNS验证,HTTP验证不支持通配符,因为CA没有办法通过HTTP去验证一个不存在的子域的响应。
DNS Challenge的原理是:Let's Encrypt要求你在域名的DNS记录中添加一条TXT记录,内容是一串随机字符串。添加成功并生效后,CA会通过公共DNS服务器查询这条记录,如果能查到正确内容,就证明你对这个域名有控制权(因为你控制了DNS解析)。这个验证方式完全绕开80端口,也是目前唯一能申请通配符证书的方式。
4.2 手动DNS验证流程
手动DNS验证适合偶尔申请,比如一年就申请一两次通配符证书的场景。命令如下:
bash复制sudo certbot certonly --manual --preferred-challenges dns -d "*.example.com" -d example.com
执行后Certbot会暂停,输出一段提示,要求你添加两条TXT记录(如果申请了两个域名)。内容大致是这样:
code复制Please deploy a DNS TXT record under the name:
_acme-challenge.example.com
with the following value:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
这时候需要登录域名DNS管理后台(阿里云、腾讯云、Cloudflare等),添加一条TXT记录,主机记录填_acme-challenge,记录值填Certbot输出的那一串随机字符串。设置TTL为300秒或更低,可以加快生效速度。
添加完成后不要急着回车。先在命令行里用DNS查询工具确认记录已经全球生效:
bash复制dig +short TXT _acme-challenge.example.com
或者:
bash复制nslookup -type=TXT _acme-challenge.example.com
查到输出中包含那串验证值,再回到Certbot命令行按回车。如果记录还没生效就回车,验证会失败,而且一旦失败,这次申请的验证token就作废了,需要重新申请生成新的token,旧token无法复用。
手动DNS验证虽然能解决问题,但体验比较原始。尤其用到通配符证书后,90天有效期意味着每3个月就要手动操作一次,很容易忘记或者漏配。而且通配符证书一旦续期失败,影响的是所有子域的HTTPS访问,风险比单域名证书大得多。所以如果准备长期使用DNS Challenge模式,强烈建议配置自动DNS插件。
4.3 自动DNS验证:以Cloudflare为例
自动DNS验证的原理,是通过DNS服务商的API,让Certbot在验证时自动添加TXT记录,验证完成后再自动删除。整个过程不需要人工干预,续期也能全自动完成。
以Cloudflare为例,Certbot有官方的DNS插件certbot-dns-cloudflare。安装步骤如下:
bash复制sudo snap install certbot-dns-cloudflare
由于用的是Snap安装的Certbot,插件和主程序是隔离的,需要手动建立连接:
bash复制sudo snap set certbot trust-plugin-with-root=ok
然后创建API凭据文件。先登录Cloudflare控制台,在“My Profile → API Tokens”页面创建一个Token,权限需要包含Zone.Zone.Read和Zone.DNS.Edit两个权限,范围选择你的域名。
创建好Token后,写入服务器上的凭据文件:
bash复制sudo mkdir -p /root/.secrets/certbot
sudo tee /root/.secrets/certbot/cloudflare.ini <<'EOF'
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
EOF
这里必须强调一个安全问题:凭据文件里放的是API Token,相当于你DNS管理后台的一把钥匙。如果权限过大,其他用户读到这个文件就能接管你的DNS解析,后果不堪设想。所以创建完文件后,必须立即设置权限:
bash复制sudo chmod 600 /root/.secrets/certbot/cloudflare.ini
600表示只有文件所有者(root)才能读写,其他用户一律不可见。这一步不是可选项,是必选项。
然后申请通配符证书:
bash复制sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials /root/.secrets/certbot/cloudflare.ini -d "*.example.com" -d example.com --email admin@example.com --agree-tos
执行后Certbot会自动调用Cloudflare API,添加TXT记录、等待生效、完成验证。整个过程看起来和Webroot模式一样快,但验证路径完全不同。申请成功后证书同样存放在/etc/letsencrypt/live/example.com/目录。
如果你用的是阿里云或腾讯云的DNS,也有对应的第三方插件,比如certbot-dns-aliyun,原理完全一样,只是API路径和凭据格式不同。用前建议先看插件的GitHub仓库,确认它支持你用的Certbot版本,再决定是否引入。毕竟第三方插件的代码质量参差不齐,安全审查一定要做。
4.4 通配符证书管理与多域名策略
拿到通配符证书后,配置Nginx的方式和普通证书一样:
nginx复制ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
一个证书可以同时用于example.com下的所有子域,比如api.example.com、blog.example.com、app.example.com,只要这些子域的流量最终都汇聚到这台服务器上,就可以共用同一个证书文件。
需要注意,通配符证书有两个边界:
*.example.com只保护一级子域,不保护example.com本身,所以申请时要同时加-d example.com和-d "*.example.com"。- 它不保护更深层的子域,比如
foo.bar.example.com,这种多级子域不能用*.example.com覆盖,需要单独申请。
如果你有多个主域,比如example.com和example.net,每个主域都要单独申请证书,不能用一张通配符证书跨主域。这是证书机制本身的限制,不是Certbot的问题。
还有一点值得注意:通配符证书的安全性比单域名证书低一些,因为私钥泄露的话,影响范围是所有子域。所以/etc/letsencrypt/live/目录的权限一定要收好,服务器上其他用户不应该有读取权限。私钥文件默认是600权限,可以定期检查确认没有被改掉。
5. 自动续期与证书生命周期管理
5.1 自动续期机制:systemd timer与cron
Let's Encrypt证书的有效期是90天,这个设计初衷是鼓励自动化,不能靠人工记忆去续期。Certbot在Snap安装完成后,会自动注册一个systemd定时器,这是Ubuntu 22.04上最干净的方式。
查看定时器是否正常运行:
bash复制sudo systemctl status certbot.timer
输出中会显示定时器下一次运行的时间。Certbot默认每12小时检查一次,只有证书剩余时间不足30天时才会执行续期,这样可以避免频繁请求CA服务器。
也可以手动触发一次续期检查:
bash复制sudo certbot renew
这条命令会检查所有已有证书,如果不需要续期就静默退出,如果需要续期就直接执行。如果你之前用apt装过certbot,它可能配置的是cron任务:
bash复制sudo crontab -l
如果看到类似0 */12 * * * certbot renew --quiet的配置,说明是cron方式。两种方式都能实现自动续期,但Snap + systemd timer明显更省心,版本更新也自动,所以我一直建议统一用Snap版,避免一台机器上两份certbot共存导致行为混乱。
5.2 续期测试与过期时间检查
自动续期配置完成后,一定要做一次演练,别等真正需要续期的时候才发现配置有问题。演练命令:
bash复制sudo certbot renew --dry-run
这条命令会模拟完整的续期流程,但不会真的替换证书。看到Congratulations或者Success字样就说明续期链路完全正常。如果报错,趁现在排查,不要拖到证书真的过期。
查看所有证书的签发时间和过期时间:
bash复制sudo certbot certificates
这条命令会列出每张证书的域名、到期日期、续期状态。输出直观,适合定期巡检。
如果只想看某张证书的过期时间,可以用更底层的命令直接读证书文件:
bash复制sudo openssl x509 -enddate -noout -in /etc/letsencrypt/live/example.com/fullchain.pem
输出:
code复制notAfter=May 15 12:00:00 2025 GMT
如果你想在自己的监控脚本里加入证书过期检查,可以用这个思路:
bash复制#!/bin/bash
domain="example.com"
cert_file="/etc/letsencrypt/live/${domain}/fullchain.pem"
expires=$(openssl x509 -enddate -noout -in "$cert_file" | cut -d= -f2)
expires_epoch=$(date -d "$expires" +%s)
now_epoch=$(date +%s)
days_left=$(( (expires_epoch - now_epoch) / 86400 ))
if [ "$days_left" -lt 15 ]; then
echo "警告:${domain} 的SSL证书将在 ${days_left} 天后过期"
fi
把这个脚本丢到cron里每天执行一次,再配合企业微信或钉钉的Webhook告警,就相当于给证书上了双保险。
5.3 续期钩子与Nginx reload
证书续期成功不代表Web服务会自动使用新证书。Nginx或Apache加载的证书文件虽然路径没变,但文件内容已经更新,必须reload一下Web服务才能真正生效。如果服务不reload,Nginx进程里缓存的还是旧证书,浏览器访问时依然会报证书过期。
Certbot提供了钩子机制来解决这个问题。在续期命令中加上--deploy-hook:
bash复制sudo certbot renew --deploy-hook "systemctl reload nginx"
--deploy-hook的含义是:只有在证书成功续期后,才执行后面的命令。如果证书还没到续期时间,命令不会执行;如果续期失败,命令也不会执行。这个机制非常安全,不会出现误操作。
更推荐的做法是把钩子写入续期配置文件,这样每次用certbot renew自动续期时都会自动执行。先看续期配置:
bash复制sudo cat /etc/letsencrypt/renewal/example.com.conf
在文件末尾找到[renewalparams]段落,加入:
ini复制renew_hook = systemctl reload nginx
保存后,后续所有自动续期只要成功,Nginx都会自动重载。这样才算把整个证书生命周期闭环起来:签发 → 配置 → 自动续期 → 重载服务。
6. 常见问题与排查技巧实录
6.1 认证失败问题速查
在实际操作中,最让人崩溃的就是证书申请失败,报错信息还不一定能看懂。我把最常见的几类问题整理成一张排查表,按图索骥能快速定位。
| 报错关键词 | 根本原因 | 解决方法 |
|---|---|---|
| Connection refused / No route to host | 80端口不通,被防火墙或安全组拦截 | 放行80端口;检查iptables、ufw、云安全组规则 |
| Invalid response from http://... | 验证文件无法通过HTTP访问到 | 检查Nginx location配置和root路径;curl直接访问验证文件确认 |
| DNS problem: NXDOMAIN | 域名解析不存在或未生效 | 用dig +short确认解析记录是否指向本机 |
| Too many certificates already issued | 触发Let's Encrypt的频率限制 | 等待一周或使用staging环境测试 |
| Cannot bind to port 80 | 端口被Web服务占用 | 停掉占用进程,或改用Webroot方式 |
排查时有个固定动作:先用curl模拟CA的访问行为。比如申请www.example.com的证书失败,手动执行:
bash复制curl -I http://www.example.com/.well-known/acme-challenge/test
如果返回404或连不上,说明验证路径确实有问题,这时候再逐层检查DNS解析、防火墙、Nginx配置。如果返回200,那问题可能出在Certbot配置参数上,重点看-w路径和Nginx的root配置是否一致。
6.2 Nginx reload后证书未生效
这种问题经常出现在第一次配置SSL的时候,现象是:证书申请成功、Nginx也正常reload、配置文件看起来没问题,但浏览器访问HTTPS还是提示证书无效。
排查顺序很重要。第一个要查的就是证书链。如果你在ssl_certificate里写的是cert.pem而不是fullchain.pem,浏览器会因为缺少中间证书而报错。判断方法很简单:
bash复制sudo nginx -T | grep ssl_certificate
如果输出里只有cert.pem,改写成:
nginx复制ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
然后重载Nginx。
第二个要查的是浏览器缓存和系统时间。浏览器缓存了旧证书或系统时间不对,也会导致验证失败。特别是用curl测试时加上-v参数:
bash复制curl -v https://example.com 2>&1 | grep subject
输出里能看到证书的subject信息,和预期的域名对得上就说明证书已正确加载。
还有一个隐蔽问题:如果你在Nginx里配置了多组server块,并且用了不同的证书文件,浏览器按端口和ServerName匹配server块,可能会匹配到错误的那块。用sudo nginx -T把实际生效配置打印出来,看到的才是真正起作用的那份配置。
6.3 Ubuntu 22.04环境下的特殊坑
在Ubuntu 22.04上操作Certbot,有几个环境相关的坑值得单独拎出来说。
第一个坑是Snap插件和主程序的权限连接。如果你用Snap装了certbot,再装DNS插件(比如certbot-dns-cloudflare),不执行sudo snap set certbot trust-plugin-with-root=ok,插件可能报了“permission denied”的错,根本原因就是Snap的隔离机制没放开。这个命令不是可选操作,是必选的。
第二个坑是apt版和Snap版的残留冲突。有些人之前用apt装过certbot,后来听别人说Snap好,又装了Snap版,结果certbot命令软链接指向了其中一个版本,而cron或timer调用的是另一个版本。两个版本行为不一致,排查时特别容易懵。解决方法是干净卸载apt版,只保留Snap版。
第三个坑是云服务器默认镜像的80端口占用。很多云厂商的Ubuntu镜像自带Nginx默认站点,或者预装了Apache占用80端口。不处理就直接跑Standalone模式,肯定是端口冲突。申请前先看一眼:
bash复制sudo ss -tlnp | grep :80
如果有程序监听80端口,先决定是停掉它还是改用Webroot模式,避免白折腾。
第四个坑是关于内网IP和证书的关系。Let's Encrypt不支持为纯内网IP签发证书,也不支持为非公网域名签发。如果你的服务只在内网访问、没有公网域名解析,那Let's Encrypt这条路走不通,只能考虑自建CA或者使用内网专用的证书方案。搞清楚自己有没有公网域名解析,是选型的第一步。
6.4 从证书小白到自动化的几条设计建议
结合我在多个服务器上的实操经验,最后给出几条设计层面的建议。
如果你管理的是有公网域名的Web服务,默认选Webroot模式,因为它是唯一一个既能保持服务在线、又能自动续期的方式。配置好--deploy-hook后,基本可以做到证书全生命周期无人值守。
如果你需要通配符证书,DNS Challenge你躲不掉。但不要停留在手动DNS验证上,即使你有耐心每90天手动配一次TXT记录,也必须意识到这样做容易出错。配置DNS API插件是更值得投入半小时去搞定的事情。
如果你管理的服务器数量比较多,证书到期时间各不相同,建议用监控平台集中扫描证书过期时间,或者至少写一个批量巡检脚本,定期SSH到各台服务器执行certbot certificates采集到期信息。别等到浏览器报错、用户投诉,才发现证书已经过期好几天了。
我在实际维护中的习惯是:证书自动续期只是底线,定时巡检才是保障。每个月第一周的巡检任务里,我会手动跑一遍renew --dry-run,顺便检查一下证书剩余天数。这个习惯坚持了几年,再也没有因为证书过期被客户追着问过了。
