返回博客

用 acme.sh 手动部署 HTTPS 证书:从申请到 Nginx 上线(含避坑)

用 acme.sh 手动部署 HTTPS 证书:从申请到 Nginx 上线(含避坑)

用 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_cfdns_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 会做两件事:

  1. 把证书、私钥复制到你指定的路径(不要直接把 ~/.acme.sh/ 里的文件配给 Nginx)
  2. 记录 --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 权限和验证逻辑的坑,按域分开签更稳


八、注意事项

  1. 邮箱:安装 acme.sh 时的 email= 会注册 ACME 账户,用于到期提醒,建议填常用邮箱。
  2. 私钥与 API Key:证书私钥、DNS 的 AccessKey 不要发给他人、不要贴到公开论坛。
  3. DNS 传播:验证失败时可加大 --dnssleep,或到 DNS 控制台确认 TXT 是否生效。
  4. 频率限制:Let's Encrypt 对同一域名签发次数有限制,调试阶段别频繁 --force
  5. 系统时间:服务器时间不准会导致验证失败,确保已开启 NTP 自动校时。
  6. EC 与 RSA 要一致--issue 用了 --keylength ec-256--install-cert 必须加 --ecc
  7. 定时任务环境: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 为准。