Certbot 证书自动化教程 / 第 8 章:自动续期
第 8 章:自动续期
8.1 为什么要自动续期
Let’s Encrypt 证书有效期仅 90 天,过期后浏览器会显示安全警告,严重影响用户体验和网站信誉。手动续期容易遗忘,因此自动化续期是生产环境的必备要求。
证书生命周期
签发 ────────── 60天 ────── 续期窗口 ── 30天 ── 到期
│ │ │
│ │ Certbot 自动 │
│ │ 尝试续期 │
│ │ │
│<── 正常使用期 ─────────────>│<─ 续期尝试期 ────>│
续期时机
| 策略 | 说明 | 建议 |
|---|---|---|
| 默认(30 天前) | 到期前 30 天自动续期 | ✅ 推荐 |
| 自定义天数 | -- renew-before-days 14 | 按需 |
| 立即续期 | certbot renew --force-renewal | 仅紧急情况 |
注意: 不要过早续期,会消耗 Let’s Encrypt 的速率限制。
8.2 Certbot 续期命令
手动续期
# 续期所有即将到期的证书
sudo certbot renew
# 续期特定证书
sudo certbot renew --cert-name example.com
# 强制续期(即使未到期)
sudo certbot renew --force-renewal
# 模拟续期(dry-run)
sudo certbot renew --dry-run
续期行为
# 查看所有证书的到期时间
sudo certbot certificates
输出示例:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
Certificate Name: example.com
Serial Number: 1234567890abcdef1234567890abcdef
Key Type: ECDSA
Domains: example.com www.example.com
Expiry Date: 2025-08-10 12:00:00+00:00 (VALID: 60 days)
Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
续期配置文件
每个证书的续期配置存储在 /etc/letsencrypt/renewal/ 目录下:
cat /etc/letsencrypt/renewal/example.com.conf
# /etc/letsencrypt/renewal/example.com.conf
[renewalparams]
authenticator = nginx
account = <account-id>
server = https://acme-v02.api.letsencrypt.org/directory
key_type = ecdsa
[[lineage]]
cert = /etc/letsencrypt/live/example.com/cert.pem
privkey = /etc/letsencrypt/live/example.com/privkey.pem
chain = /etc/letsencrypt/live/example.com/chain.pem
fullchain = /etc/letsencrypt/live/example.com/fullchain.pem
8.3 systemd Timer(推荐)
systemd timer 是现代 Linux 系统推荐的定时任务管理方式,比 cron 更灵活、可靠。
检查默认 systemd timer
# Snap 安装的 Certbot 会自动创建 systemd timer
sudo systemctl list-timers | grep certbot
# 查看 timer 详情
sudo systemctl cat certbot.timer
sudo systemctl cat certbot.service
Certbot 默认 systemd timer
# /lib/systemd/system/certbot.timer
[Unit]
Description=Certbot timer
[Timer]
# 每天随机两次检查
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=43200
Persistent=true
[Install]
WantedBy=timers.target
# /lib/systemd/system/certbot.service
[Unit]
Description=Certbot
Documentation=file:///usr/share/doc/certbot/manual/html/index.html
Documentation=https://certbot.eff.org/docs
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot -q renew
# 注意:-q 参数表示安静模式,仅在有错误或成功续期时输出
启用 systemd timer
# 启用 timer
sudo systemctl enable certbot.timer
# 启动 timer
sudo systemctl start certbot.timer
# 查看 timer 状态
sudo systemctl status certbot.timer
# 查看下次运行时间
sudo systemctl list-timers certbot.timer
自定义 systemd timer
# 创建自定义 timer
sudo tee /etc/systemd/system/certbot-custom.timer > /dev/null << 'EOF'
[Unit]
Description=Certbot renewal custom timer
[Timer]
# 每天凌晨 2:30 和下午 2:30 各检查一次
OnCalendar=*-*-* 02,14:30:00
RandomizedDelaySec=3600
Persistent=true
[Install]
WantedBy=timers.target
EOF
# 创建对应的 service
sudo tee /etc/systemd/system/certbot-custom.service > /dev/null << 'EOF'
[Unit]
Description=Certbot renewal custom service
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot -q renew
PrivateTmp=true
EOF
# 启用自定义 timer
sudo systemctl daemon-reload
sudo systemctl enable certbot-custom.timer
sudo systemctl start certbot-custom.timer
自定义 systemd timer 参数说明
| 参数 | 说明 | 示例 |
|---|---|---|
OnCalendar | 定时触发规则 | *-*-* 02:00:00(每天凌晨 2 点) |
RandomizedDelaySec | 随机延迟(秒) | 43200(最多延迟 12 小时) |
Persistent | 错过的任务是否补执行 | true |
OnBootSec | 开机后多久触发 | 5min |
OnUnitActiveSec | 上次触发后多久再次触发 | 12h |
8.4 Cron 定时任务
使用 cron 进行续期
# 编辑 root 的 crontab
sudo crontab -e
添加以下内容:
# 每天凌晨 2 点和下午 2 点各执行一次续期
0 2,14 * * * certbot -q renew
Cron 表达式说明
分 时 日 月 周 命令
│ │ │ │ │ │
│ │ │ │ │ └── 命令
│ │ │ │ └──── 星期几 (0-7, 0和7都是周日)
│ │ │ └────── 月份 (1-12)
│ │ └──────── 日 (1-31)
│ └────────── 小时 (0-23)
└──────────── 分钟 (0-59)
推荐的 Cron 配置
# 每天凌晨 3:00 执行续期,3:05 重启 Web 服务器
0 3 * * * certbot -q renew --deploy-hook "systemctl reload nginx"
5 3 * * * systemctl reload nginx
# 每天随机时间执行(避免与其他人同时请求)
# 使用 RANDOM_DELAY
RANDOM_DELAY=30
0 2 * * * certbot -q renew
Cron vs systemd timer
| 特性 | Cron | systemd Timer |
|---|---|---|
| 安装复杂度 | 简单 | 中等 |
| 随机延迟 | 需要脚本支持 | 原生 RandomizedDelaySec |
| 错过补执行 | ❌ | Persistent=true |
| 日志集成 | 需要手动配置 | 自动集成 journald |
| 依赖管理 | ❌ | 支持 After/Requires |
| 适用场景 | 简单需求 | 推荐 |
8.5 续期钩子
续期钩子(Hooks)允许在续期过程中执行自定义操作,如重启 Web 服务器、发送通知等。
命令行钩子
# 续期前执行(pre-hook)
sudo certbot renew --pre-hook "systemctl stop nginx"
# 续期后执行(post-hook)
sudo certbot renew --post-hook "systemctl start nginx"
# 续期成功后执行(deploy-hook)
sudo certbot renew --deploy-hook "systemctl reload nginx"
# 组合使用
sudo certbot renew \
--pre-hook "systemctl stop nginx" \
--post-hook "systemctl start nginx" \
--deploy-hook "systemctl reload nginx"
钩子类型对比
| 钩子类型 | 执行时机 | 执行条件 | 典型用途 |
|---|---|---|---|
--pre-hook | 续期尝试前 | 每次都执行 | 停止 Web 服务器(Standalone) |
--post-hook | 续期尝试后 | 每次都执行 | 启动 Web 服务器 |
--deploy-hook | 证书更新成功后 | 仅续期成功时 | 重载 Web 服务器、发送通知 |
在续期配置文件中设置钩子
# /etc/letsencrypt/renewal/example.com.conf
[renewalparams]
authenticator = webroot
webroot_path = /var/www/certbot
[renewalhooks]
pre_hook = /usr/local/bin/certbot-pre-renew.sh
post_hook = /usr/local/bin/certbot-post-renew.sh
deploy = /usr/local/bin/certbot-deploy.sh
使用 hook 脚本目录
# 创建钩子脚本目录
sudo mkdir -p /etc/letsencrypt/renewal-hooks/{pre,post,deploy}
# 在对应目录下放置脚本(自动执行)
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh > /dev/null << 'EOF'
#!/bin/bash
systemctl reload nginx
echo "[$(date)] Nginx reloaded after certificate renewal" >> /var/log/certbot-hooks.log
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
8.6 续期测试
Dry Run 测试
# 测试所有证书的续期流程(不实际续期)
sudo certbot renew --dry-run
# 输出示例:
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Processing /etc/letsencrypt/renewal/example.com.conf
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Simulating renewal of an existing certificate for example.com
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# The dry run was successful.
测试特定证书
sudo certbot renew --dry-run --cert-name example.com
测试钩子脚本
# 测试续期并执行钩子
sudo certbot renew --dry-run \
--deploy-hook "echo 'Deploy hook executed'"
# 手动测试钩子脚本
sudo /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
定期测试建议
# 每月执行一次 dry-run 测试(可以放在 cron 中)
0 4 1 * * certbot renew --dry-run 2>&1 | tee -a /var/log/certbot-test.log
8.7 续期日志
日志位置
| 日志文件 | 内容 |
|---|---|
/var/log/letsencrypt/letsencrypt.log | Certbot 主日志 |
/var/log/letsencrypt/letsencrypt.log.1 | 上一次的日志(轮转) |
| journald (systemd) | systemd timer/service 日志 |
查看日志
# 查看 Certbot 日志
sudo cat /var/log/letsencrypt/letsencrypt.log
# 查看最近的日志
sudo tail -n 50 /var/log/letsencrypt/letsencrypt.log
# 查看 systemd timer 日志
sudo journalctl -u certbot.service
# 查看最近的 timer 运行记录
sudo journalctl -u certbot.timer --since "1 week ago"
# 实时查看
sudo journalctl -fu certbot.timer
日志轮转
Certbot 自带日志轮转配置,可在 /etc/letsencrypt/cli.ini 中调整:
# 保留最近 10 个日志文件
max-log-backups = 10
8.8 续期失败排查
常见失败原因
| 错误 | 原因 | 解决方案 |
|---|---|---|
| 端口 80 被占用 | 其他服务占用了 80 端口 | 配置 pre/post hook 停止/启动服务 |
| 防火墙阻止 | 80 端口不可达 | 检查防火墙规则 |
| 域名解析错误 | DNS 记录变更 | 检查域名解析 |
| 速率限制 | 短时间内申请太多证书 | 等待限制重置或使用 staging |
| 配置文件损坏 | 续期配置被修改 | 检查 /etc/letsencrypt/renewal/ |
| Web 服务器未运行 | Nginx/Apache 未启动 | 确保服务正常运行 |
| 权限不足 | 脚本权限问题 | 检查脚本权限和 SELinux |
排查步骤
# 1. 查看证书状态
sudo certbot certificates
# 2. 执行 dry-run 测试
sudo certbot renew --dry-run
# 3. 查看详细日志
sudo certbot renew --dry-run --debug-challenges
# 4. 检查续期配置
cat /etc/letsencrypt/renewal/example.com.conf
# 5. 检查 Web 服务器状态
sudo systemctl status nginx
sudo nginx -t
手动修复续期配置
# 如果续期配置损坏,可以删除并重新创建
sudo rm /etc/letsencrypt/renewal/example.com.conf
# 重新申请证书(会自动创建续期配置)
sudo certbot --nginx -d example.com
8.9 续期最佳实践
- 每天执行两次续期检查: 早晚各一次,使用随机延迟
- 使用 systemd timer: 比 cron 更可靠,支持日志和依赖管理
- 定期 dry-run 测试: 每月至少一次,确保续期流程正常
- 配置 deploy-hook: 续期成功后自动重载 Web 服务器
- 监控证书过期: 使用外部监控工具检查证书有效期
- 保留日志: 方便排错和审计