用 acme.sh 手动部署 HTTPS 证书:从申请到 Nginx 上线(含避坑)
免费 HTTPS 证书里,Let's Encrypt 用得最多。命令行工具 acme.sh 轻量、支持自动化 DNS 验证,特别适合泛域名(*.example.com)。下面按纯手工命令,从零到上线,顺序做即可。
一、先搞清两件事
1. 验证方式选哪种?
| 方式 | 原理 | 适合场景 |
|---|---|---|
| HTTP-01 | 在网站 80 端口放验证文件 | 单个域名、服务器能对外提供 HTTP |
| DNS-01 | 在 DNS 加 _acme-challenge TXT 记录 |
泛域名、内网服务、多台机器 |
泛域名只能走 DNS-01。 不能靠 Nginx 里随便配一个 location 就签出
*.domain。
2. 证书装在哪、Nginx 指哪?
先定一个固定目录,例如:
/etc/nginx/ssl/example.com/fullchain.cer
/etc/nginx/ssl/example.com/example.com.key
路径定好后尽量别改。续期时 acme.sh 用 --install-cert 覆盖同名文件,Nginx 配置不用动。
二、安装 acme.sh
curl https://get.acme.sh | sh -s email=your@email.com
source ~/.bashrc # 或重新登录 shell
acme.sh --version
安装完成后可使用全局命令 acme.sh,程序本体在 ~/.acme.sh/。
三、方式 A:DNS 泛域名(推荐)
1. 准备 DNS API
域名在哪个 DNS 服务商,就用该商的 API。以阿里云为例:在 RAM 创建子账号,授予 DNS 解析相关权限(如可添加/删除 TXT 记录),获取 AccessKey。
2. 设置环境变量
acme.sh 各 DNS 插件认的变量名不同,签发前在终端执行 export:
阿里云:
export Ali_Key="你的AccessKeyId"
export Ali_Secret="你的AccessKeySecret"
Cloudflare(推荐 API Token):
export CF_Token="你的Token"
DNSPod:
export DP_Id="你的ID"
export DP_Key="你的Token"
更多 DNS 商见官方 Wiki:acme.sh DNS API。
3. 签发证书
acme.sh --set-default-ca --server letsencrypt
acme.sh --issue \
--dns dns_ali \
--dnssleep 20 \
-d example.com \
-d '*.example.com' \
--keylength ec-256
说明:
--dns dns_ali换成你的 DNS 插件名(如dns_cf、dns_dp)--dnssleep等待 DNS 传播,国内一般 20 秒;Cloudflare 可试 30~60- 同时写
-d example.com和-d '*.example.com'得到泛域名证书
4. 安装到 Nginx 使用的路径
mkdir -p /etc/nginx/ssl/example.com
acme.sh --install-cert -d example.com --ecc \
--key-file /etc/nginx/ssl/example.com/example.com.key \
--fullchain-file /etc/nginx/ssl/example.com/fullchain.cer \
--reloadcmd "nginx -t && nginx -s reload"
--install-cert 会做两件事:
- 把证书、私钥复制到你指定的路径(不要直接把
~/.acme.sh/里的文件配给 Nginx) - 记录
--reloadcmd,以后续期成功会自动执行这条命令
若签发时用的是 RSA(--keylength 2048),去掉 --install-cert 里的 --ecc。
四、方式 B:HTTP 单域名(不能签泛域名)
站点已对外提供 80 端口、只签单个域名时可用:
acme.sh --issue -d example.com -d www.example.com -w /var/www/html
-w 为网站根目录,acme.sh 会在其下写入 .well-known/acme-challenge/ 验证文件。
Nginx 需保证该路径可访问:
location /.well-known/acme-challenge/ {
root /var/www/html;
}
然后同样 --install-cert 安装到固定路径并注册 --reloadcmd。
五、配置 Nginx
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/nginx/ssl/example.com/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/example.com/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_timeout 1d;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
子域(如 admin.example.com):若证书是 *.example.com 泛域名,另建 server 块、改 server_name 即可,ssl_certificate 路径与根域相同。
nginx -t && nginx -s reload
六、配置自动续期
Let's Encrypt 证书有效期约 90 天,必须自动续期。
crontab -e
添加(日志路径可自定):
0 3 * * * acme.sh --cron >> /var/log/acme.log 2>&1
acme.sh --cron 会:
- 扫描本机已注册的所有证书
- 临近过期时用当初相同方式(DNS / HTTP)自动续签
- 续签成功后执行
--install-cert时注册的--reloadcmd
手动测试续期(慎用 --force,会真的重签):
acme.sh --renew -d example.com --force
七、多个域名怎么处理?
每个独立的根域通常各签一张证,各执行一次 --issue + --install-cert:
# example.com
acme.sh --issue --dns dns_ali -d example.com -d '*.example.com' ...
acme.sh --install-cert -d example.com --key-file ... --fullchain-file ...
# another.com
acme.sh --issue --dns dns_ali -d another.com -d '*.another.com' ...
acme.sh --install-cert -d another.com --key-file ... --fullchain-file ...
一次 --issue 里混搭多个不相关的根域,容易踩 DNS 权限和验证逻辑的坑,按域分开签更稳。
八、注意事项
- 邮箱:安装 acme.sh 时的
email=会注册 ACME 账户,用于到期提醒,建议填常用邮箱。 - 私钥与 API Key:证书私钥、DNS 的 AccessKey 不要发给他人、不要贴到公开论坛。
- DNS 传播:验证失败时可加大
--dnssleep,或到 DNS 控制台确认 TXT 是否生效。 - 频率限制:Let's Encrypt 对同一域名签发次数有限制,调试阶段别频繁
--force。 - 系统时间:服务器时间不准会导致验证失败,确保已开启 NTP 自动校时。
- EC 与 RSA 要一致:
--issue用了--keylength ec-256,--install-cert必须加--ecc。 - 定时任务环境:crontab 里的 PATH 可能和登录终端不同;若找不到
acme.sh,可写全路径~/.acme.sh/acme.sh --cron。
九、常见坑
坑 1:Unknown parameter: --dns-sleep
正确写法是 --dnssleep(一个词),不是 --dns-sleep。
坑 2:只 issue 不 install-cert
--issue 成功后证书在 ~/.acme.sh/ 下。Nginx 必须配 --install-cert 复制出来的路径,否则续期、reload 都会对不上。
坑 3:Nginx 仍报证书错误或过期
- 配置里仍指向旧路径或
~/.acme.sh/目录 - 改完配置没执行
nginx -t && nginx -s reload - 续期失败但没看
/var/log/acme.log
坑 4:续期后 reload 失败
--reloadcmd 里若调用脚本,脚本要有执行权限;或直接用:
--reloadcmd "nginx -t && nginx -s reload"
坑 5:泛域名用了 HTTP-01
HTTP-01 无法验证 *.example.com,必须 DNS API。
坑 6:DNS API 权限不足
只能读不能写 TXT → 一直超时。检查子账号是否有添加/删除 TXT 记录权限。
坑 7:DNS 插件与环境变量不匹配
--dns dns_ali 却配置了 Cloudflare 的 Token;或域名在 A 商、API 却是 B 商。
坑 8:以为续期要改 Nginx
--install-cert 的路径不变时,续期只更新文件内容,Nginx ssl 配置不用改。
坑 9:触发 Let's Encrypt 限流
短时间多次失败或强制重签会被限流,需等待后再试。本地调试可用测试 CA:
acme.sh --issue --test --dns dns_ali ...
测试证书浏览器不信任,仅用于验证流程。
坑 10:防火墙关死 80 端口
DNS 签发不依赖 80,但用户访问 HTTP→HTTPS 跳转、部分健康检查仍需要 80。443 配好后别忘记 80 跳转。
坑 11:wildcard 引号
shell 里泛域名建议写成 -d '*.example.com'(带引号),避免 * 被 shell 展开。
坑 12:查看已安装证书与续期时间
acme.sh --list
可确认域名、到期日、证书文件位置。
十、小结
纯手工部署,记住四条命令链:
| 步骤 | 命令 | 作用 |
|---|---|---|
| 1 | acme.sh --issue ... |
验证域名并签发 |
| 2 | acme.sh --install-cert ... |
复制到固定路径 + 注册 reload |
| 3 | 编辑 Nginx | ssl_certificate 指向固定路径 |
| 4 | crontab acme.sh --cron |
自动续期 |
把路径固定、参数拼对、DNS 权限给够,绝大多数问题都能避免。遇到报错,先看终端输出和续期日志,再对照上文「常见坑」逐项排查。
具体 DNS API 参数以 acme.sh 官方 Wiki 为准。
