HTTPS 证书申请与自动续期:acme.sh + Let's Encrypt 完整记录
证书过期这事的惩罚机制很公平:你忘了续,全站用户帮你发现。
我的博客和几个小服务全跑在自己服务器上,最早用宝塔点按钮签证书,有一次续期失败没发现,站上挂着"不安全"红标整整两天。后来全部换成 acme.sh 全自动签发续期,两年多零事故。这篇是完整复刻:从装 acme.sh 到签发、部署、自动续期、常见报错,一条链讲完。
一、方案选型:为什么是 acme.sh
免费证书就两个主流来源:Let's Encrypt 和 ZeroSSL(acme.sh 默认会在这两者间自动选择)。签发工具我对比过三个:
| 工具 | 优点 | 缺点 |
|---|---|---|
| certbot | 官方出品、文档多 | Python 依赖重,自动续期要配 systemd timer |
| acme.sh | 纯 shell、零依赖、DNS API 支持最全 | 文档偏散 |
| 宝塔/面板 | 点按钮 | 黑盒,续期失败不通知,出问题难排查 |
选 acme.sh 的核心理由:支持几十家 DNS 服务商的 API 自动验证 ,这意味着通配符证书(*.example.com)和内网机器都能签,而且续期完全无人值守。
二、安装与签发:两条路
安装就一行:
bash
curl https://get.acme.sh | sh -s email=my@example.com
source ~/.bashrc
acme.sh --version
路线 A:HTTP 验证(适合单机、能开 80 端口)
前提:域名已解析到这台服务器,80 端口可访问。
bash
acme.sh --issue -d example.com -d www.example.com \
--webroot /var/www/html
如果你用 nginx 且懒得配 webroot,用 standalone 模式(会临时占用 80 端口,签完自动释放):
bash
systemctl stop nginx # 或者用 --nginx 模式免停
acme.sh --issue -d example.com --standalone
systemctl start nginx
路线 B:DNS API 验证(推荐,能签通配符)
以 DNSPod(腾讯云)为例,先在控制台创建 API 密钥,然后:
bash
export DP_Id="123456"
export DP_Key="xxxxxxxx"
acme.sh --issue --dns dns_dp -d example.com -d "*.example.com"
acme.sh 会自动添加 TXT 记录、等传播、验证、删记录,全程无感。密钥会被保存在 ~/.acme.sh/account.conf,续期时自动复用------这就是"自动"的根基。阿里云是 --dns dns_ali(需要 AccessKey),Cloudflare 是 --dns dns_cf(用 Global API Key 或 Token)。
安全提醒:用于签证书的 API Key 权限只给 DNS 编辑就够了,别用账号主密钥。Key 泄露等于域名解析权泄露。
三、部署到 Nginx:不要直接用 ~/.acme.sh 里的文件
acme.sh 生成的证书在 ~/.acme.sh/example.com_ecc/ 下,但官方明确说这个目录是内部工作目录,结构可能变。正确姿势是用 --install-cert 让它把证书拷贝 到你的稳定路径,并且续期后自动 reload nginx:
bash
mkdir -p /etc/nginx/certs/example.com
acme.sh --install-cert -d example.com --ecc \
--key-file /etc/nginx/certs/example.com/key.pem \
--fullchain-file /etc/nginx/certs/example.com/fullchain.pem \
--reloadcmd "nginx -s reload"
--reloadcmd 是整个自动化的点睛之笔:证书续期成功后自动执行。没有它,续期再成功 nginx 还在用内存里的旧证书,一样全站报错。
nginx 站点配置(关键部分):
nginx
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/nginx/certs/example.com/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/example.com/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# 80 跳 443 由另一个 server 块处理
}
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
四、自动续期:装完就忘,但要会检查
acme.sh 安装时已经自动写入了 cron(crontab -l 能看到一行 acme.sh --cron)。它每次运行会检查证书剩余有效期,不足 30 天才真正续期,续期成功后执行你的 --reloadcmd。
装完做一次"续期演练",确认整条链路真的通:
bash
acme.sh --renew -d example.com --ecc --force # 强制立即续期一次
acme.sh --list # 看证书状态和剩余天数
如果 --list 里 Le_NextRenewTime 正常往后推、nginx reload 没报错,这条链路就是通的。建议在日历上放一个 60 天一次的提醒 ,跑一次 acme.sh --list,让"自动"也有一道人工保险。
五、报错速查:我踩过的和群友问爆的
| 现象 | 原因与解法 |
|---|---|
Timeout during connect (HTTP 验证) |
80 端口没开放/被防火墙挡。云服务器安全组放行 80;国内服务器 80/443 需备案,没备案用 DNS 验证绕开 |
DNS 验证一直 pending |
TXT 记录传播慢,多等 2~5 分钟;用 dig TXT _acme-challenge.example.com 确认记录已生效 |
dns_dp: API error |
DP_Id/DP_Key 错了,或者密钥是账号密码不是 API 密钥 |
| 浏览器报证书链不全 | 用的是 cert.pem 而不是 fullchain.pem,换成全链文件 |
| 续期成功但网站还是旧证书 | 忘了 --reloadcmd 或 reload 没执行成功,acme.sh --renew -d xx --force 后看日志 |
频率限制 too many certificates |
Let's Encrypt 对同域名每周 5 张,测试加 --test(测试环境务必用 staging,别耗真额度) |
查看详细日志永远是第一步:
bash
tail -50 ~/.acme.sh/acme.sh.log
六、进阶:多机部署与内网机器
证书签在一台机器上,其他机器怎么用?两个思路:
- 集中部署 :主机器续期后,
--reloadcmd里用 scp/rsync 把证书推给其他机器并触发对端 reload(需要对端配置免密登录)。适合 2~5 台的规模。 - 每台自己签:DNS 验证方式下每台机器都能独立走完整流程,互不依赖,规模大了之后反而更稳。
内网机器(无公网 80)只能走 DNS 验证,这也是我推荐 DNS API 路线的根本原因------它对网络环境零要求。
七、总结
HTTPS 的配置成本其实一次性只有 15 分钟:装 acme.sh → DNS API 签发(可带通配符)→ --install-cert 部署 + 自动 reload → 演练一次续期。做完之后这件事就从你的运维清单里消失了,剩下的只是每两个月扫一眼 acme.sh --list。今晚就可以把那个还挂着"不安全"的站处理掉,顺便把 80 端口 301 到 443,一步到位。