🔒 第02章 · Let's Encrypt 签发与续期
certbot nginx/webroot 自动续期
02 | Let's Encrypt 签发与续期¶
上一章讲了证书和信任链,但真正让人头疼的是"证书从哪来、到期怎么办"。这一章用 certbot 把这事自动化:免费签发、自动续期、出了故障怎么自查。文中所有命令和路径都取自本机真实环境——
bangdream.xyz、dsh.bangdream.xyz、gitlab.bangdream.xyz、lovelive.top四张证书就是靠这套流程跑起来的。
Let's Encrypt 是什么¶
Let's Encrypt 是一个免费的、自动化的、开放的证书颁发机构(CA)。它给域名签发证书,通过 ACME 协议证明"你确实控制这个域名",然后签发一张 90 天有效的证书。
为什么是 90 天?因为短有效期 + 自动续期 = 安全。万一私钥泄漏,90 天后自动换新,泄漏面可控;也倒逼大家把续期自动化,避免"证书忘续导致服务瘫痪"。
安全提示:看到"证书要钱、30/60/90 天、还只给半年"的商业 CA 时别慌。Let's Encrypt 免费且自动,90 天是设计使然,不是缺陷——关键在于把续期做成自动化,而不是靠人记得。
certbot 安装¶
certbot 是 Let's Encrypt 官方推荐的客户端,可插拔的 authenticator(怎么验证域名)和 installer(怎么装证书)。
# Debian/Ubuntu
apt install -y certbot python3-certbot-nginx
# 查看本机版本(本机为 2.9.0)
certbot --version
ACME 验证方式:证明"域名是你的"¶
让 CA 相信你拥有域名,靠的是在域名可控的位置展示一个令牌。certbot 内置三种主要验证:
| 验证方式 | 原理 | 适用场景 | 本机用的是什么 |
|---|---|---|---|
| HTTP-01 | 在 /.well-known/acme-challenge/ 放一个令牌文件,CA 用 HTTP 访问你的 80 端口验证 |
有 80 端口的普通站点 |
否(本机用 nginx) |
| DNS-01 | 在 _acme-challenge 的 DNS TXT 记录里放凭证 |
通配符 *.domain 或纯 API 场景 |
否 |
| TLS-ALPN-01 | 在 443 端口的 handshake 里放一个特殊 ALPN 扩展 |
需要 443 又不想占 80 的场景 | 否 |
certbot 会和 authenticator 插件配合去落地这些文件。本机走的是 nginx 认证器——它自动操作 nginx 配置文件完成 HTTP-01。
用 nginx 认证器签发(本机真实做法)¶
# certbot 会自动读 nginx 配置、生成临时验证文件、申请并安装证书
certbot --nginx -d bangdream.xyz -d www.bangdream.xyz
# 一次性签多个域名,每个 -d 都进 SAN(上面的清单可叠加)
certbot --nginx -d gitlab.bangdream.xyz -d dsh.bangdream.xyz
-d可以重复。一个 certbot 命令签多个域名,最终是一张 SAN 证书,而不是每个域名一张。你看到的fullchain.pem里 SAN 会是DNS:bangdream.xyz, DNS:www.bangdream.xyz——这就是一个证书盖多个活。
certbot 装完之后对配置做了什么¶
签发成功,nginx installer 会自动往你的 server 块里塞四条 TLS 配置(本机 bangdream.xyz.conf 里可见):
# managed by Certbot —— 这几行是 certbot 自动写入的
ssl_certificate /etc/letsencrypt/live/bangdream.xyz/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/bangdream.xyz/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
同时它把一个 80 端口的 server 块改成 301 跳转到 HTTPS,并在文件里留下 # managed by Certbot 标记。
易错点:不要删掉
# managed by Certbot标记,也不要手动和它抢着改这些行。 certbot 靠这些注释来判断"这段是我管的",你改动后下一轮续期会报An unexpected error occurred。想自定义 TLS 参数,去改options-ssl-nginx.conf或加自己的 server 块,别动 certbot 管理的行。
证书目录结构¶
签发后,真实文件落在 /etc/letsencrypt/:
/etc/letsencrypt/
├── live/<domain>/ # 每域名一张证书的"当前"链接目录(注意是软链,别删!)
│ ├── cert.pem -> ../../archive/<domain>/cert1.pem
│ ├── chain.pem -> ../../archive/<domain>/chain1.pem
│ ├── fullchain.pem -> ../../archive/<domain>/fullchain1.pem
│ └── privkey.pem -> ../../archive/<domain>/privkey1.pem
├── archive/<domain>/ # 每轮续期"历史版本"都保留在这,方便回滚
├── renewal/<domain>.conf # 每张证书的续期配置
└── options-ssl-nginx.conf # certbot 的 TLS 安全参数
易错点:
live/下全是软链接,指向archive/。配置里永远引用live/目录,不要引archive/里的数字版本。这样每次续期后live/自动指向最新版本,无需改配置。手动删除或 mv 这些链接会导致证书失效。
自动续期:certbot.timer¶
证书 90 天到期,但你不用手动续。certbot 用 systemd timer 每天运行两次,并在证书 剩余不足 30 天(renewal 配置里的 renew_before_expiry = 30 days)时才真正去续,其余时间跳过,节约 ACME 资源。
# 看本机 timer 是否启用(本机显示 active,每日两次)
systemctl status certbot.timer
systemctl list-timers | grep certbot
# 模拟续期:不真正向 CA 请求,只是走一遍全流程,验证配置/权限没问题
certbot renew --dry-run
本机
certbot renew --dry-run的输出会显示四张证书全部success——这是最该定期跑的健康检查。它证明域名验证、续期参数、部署钩子都在,真正的自动续期才靠谱。
续期配置长什么样¶
每张证书一份独立的 .conf,本机 bangdream.xyz 的续期配置:
# renew_before_expiry = 30 days
version = 2.9.0
archive_dir = /etc/letsencrypt/archive/bangdream.xyz
cert = /etc/letsencrypt/live/bangdream.xyz/cert.pem
privkey = /etc/letsencrypt/live/bangdream.xyz/privkey.pem
chain = /etc/letsencrypt/live/bangdream.xyz/chain.pem
fullchain = /etc/letsencrypt/live/bangdream.xyz/fullchain.pem
[renewalparams]
account = 18ea54f7687fd356cd91a72e4c5632c6
authenticator = nginx
installer = nginx
server = https://acme-v02.api.letsencrypt.org/directory
key_type = ecdsa
key_type = ecdsa表示本机用的是 ECDSA 证书——比 RSA 更短的密钥、更快的握手,TLS 1.3 下的现代选择。authenticator = nginx说明续期时 certbot 会用它去写 nginx 验证端口。
场景案例:让 GitLab 自己的证书"闭嘴",统一给外层签发¶
服务器上的 GitLab(gitlab.bangdream.xyz)走的是外层 nginx 终止 TLS 的方案。GitLab 自己不开 Let's Encrypt,证书统一由外层 nginx 管:
浏览器 → 443/TLS → 外层 nginx(持有 cerbot 签的证书) → 内网 http://127.0.0.1:8090 → GitLab
对应 gitlab.rb 里的配置(本机实测):
# /etc/gitlab/gitlab.rb
external_url 'https://gitlab.bangdream.xyz'
nginx['listen_port'] = 8090 # GitLab 自己的 nginx 只监听内网端口
letsencrypt['enable'] = false # 让 GitLab 不要自己签证书,交给外层
易错点:
letsencrypt['enable'] = false必须显式写,否则 GitLab 内置 certbot 会尝试自己签,和你的外层证书打架、还可能申请失败。同时记得在gitlab.rb改动后执行gitlab-ctl reconfigure。这类"内层不自签"的委托模式,前提是外层 nginx 代理时把X-Forwarded-Proto $scheme正确传给内层,内层才知道该用什么协议。安全提示:给内层服务(如 GitLab、后端 API)做 TLS 委托时,服务内部就走 http 127.0.0.1 明文是常见的——因为只在回环网卡上,不暴露公网,可以接受;但千万不要让内层服务监听
0.0.0.0,否则会绕过外层直接裸奔。
certbot 其他常用子命令¶
| 命令/工具 | 作用 | 示例 |
|---|---|---|
certbot --nginx -d <域> |
用 nginx 认证器签发 | certbot --nginx -d bangdream.xyz |
certbot certonly |
只签证书不改 nginx(与手动验证配合) | certbot certonly --webroot -w /var/www/html -d x.com |
certbot renew |
对所有到期的证书执行续期 | certbot renew |
certbot renew --dry-run |
模拟续期,验证流程 | certbot renew --dry-run |
certbot certificates |
列出所有证书与到期时间 | certbot certificates |
certbot delete --cert-name <域> |
删除某张证书 | certbot delete --cert-name bangdream.xyz |
certbot list |
列出证书 | certbot list |
排查:续期失败时先看这几处¶
# 1. 证书到期时间
certbot certificates | grep -A3 bangdream.xyz
# 2. 模拟续期,看具体哪一步报错
certbot renew --dry-run
# 3. 是否被 rate limit(每域名每周签发有限次)
cat /var/log/letsencrypt/letsencrypt.log | grep -i "rate" | tail -5
# 4. 域名解析和 80 端口是否可达(Certbot 验证依赖)
dig +short bangdream.xyz; ss -ltnp | grep :80
易错点:HTTP-01 验证要用 80 端口,如果
80被防火墙拦了或 nginx 没监听 80,ACME 验证必然失败。本机方案里 certbot 加的这段80 -> 443301 server 块,同时也保证 ACME 挑战能被正确响应。
下一章:03-nginx-ssl → 把 certbot 装好的 TLS 配置逐行拆开,搞懂 Nginx 到底在哪一层做了加密。