acme.sh + 阿里云DNS API:SSL证书自动续期完全指南
你还在每 90 天手动续一次 SSL 证书吗如果是我猜你已经设了好几个“证书还有 XX 天过期”的闹钟甚至可能哪天手一抖忘了第二天就迎来浏览器那个刺眼的红色警告页面。我自己手上十几个域名跑着 HTTPS 服务以前每逢证书临期就得登录后台、点续期、下载证书、传服务器、reload Nginx流程熟练得像流水线一样。后来我把这套活儿交给了 acme.sh 配合阿里云 DNS API让证书续期变成服务器自己每天检查、自动签发、自动部署我才算真的从这件事里解脱出来。这篇文章不是那种“复制粘贴能跑就行”的教程它会带你从手动续期的痛点出发把 ACME 两种校验方式的原理讲清楚再一步步演示如何在阿里云创建 RAM 子用户、配置 DNS API 权限、用 acme.sh 签发证书、把证书装进 Nginx最后验证自动续期链路是否真的能跑通。无论你是刚接触 HTTPS 的小白还是已经被手动续期折磨到想写脚本的运维这套方案都适合你。看完之后证书过期这件事基本可以从你的待办清单里划掉了。1. 手动续期到底痛在哪为什么必须自动化1.1 90天有效期和人类记性之间的矛盾先说个不少人都忽略的事实Let’s Encrypt 这类免费证书把有效期设计成 90 天不是拍脑袋定的而是出于安全考虑。证书有效期越短就算私钥意外泄露攻击者能用它伪装你的时间窗口也越短。站在 CA 的角度这是合理的站在运维的角度这就意味着每 90 天你就得把“续期”这件事重新捡起来一次。手动续期看起来就几步登录证书管理后台、选择域名、生成新证书、把证书文件下载下来、上传到服务器、修改或确认 Nginx 配置、reload然后还要找个在线工具检测一下证书链是否完整。一次两次没问题但几十个域名、每 90 天来一轮纯靠人肉去扛迟早要出事。我自己就经历过一次某个客户域名的证书在我出差那几天悄悄过期结果客户早上打开网站看到大红叉电话直接打过来场面一度非常尴尬。更麻烦的是90 天这个周期放在人的工作节奏里特别容易被忽略。第一天你会记着第二周你可能还记得到第七周、第八周的时候手头事情一多你就开始指望“日历提醒”结果提醒弹出来那天你正好在开会想着“下午再弄”然后就没有然后了。所以后来我彻底想通了这种重复性高、规则明确、出错代价又不小的事情就应该交给程序去干人不要参与。1.2 为什么是acme.sh 阿里云DNS API而不是其他方案市面上做证书自动化的工具不少我实际用下来最顺手的组合就是 acme.sh 加阿里云 DNS API。先说说为什么不是别的方案。第一种备选是用阿里云控制台里的免费证书。这个方式对小白最友好但有个硬伤个人免费证书有效期也只有 3 个月现在有些产品线已经缩短到 90 天而且申请流程还是要登录网页去点不支持泛域名更关键的是没有开放的 API 让你全自动签发和部署。你依然绕不开“每隔一阵去网页上操作一次”这件事只是把操作对象从 Let’s Encrypt 换成了阿里云控制台而已。第二种备选是 Certbot。Certbot 功能很强但它是 Python 生态安装时要拉一堆依赖DNS 插件层面虽然官方和社区提供了很多插件但针对阿里云 DNS 的插件配置起来不算省心。如果你只是单域名、并且 80 端口方便对外开放Certbot 没问题但一旦涉及泛域名、涉及国内云厂商的解析 API体验就会打折。第三种就是我最终用的 acme.sh。它是纯 Shell 实现的 ACME 客户端安装就是一条 curl 命令不依赖 Python、不依赖 Node装完自带 cron 定时任务每天自动检查证书状态到期前自动续期。更重要的是它对 DNS API 的支持非常全阿里云、腾讯云、Cloudflare 这些常见服务商都有现成的插件。你只要配置好 AccessKey它就能在签发前自动去阿里云解析里加一条 TXT 记录等 CA 验证完成后再自动删掉整个过程不需要你碰网页控制台。这里多解释一句为什么强调“阿里云 DNS API”。ACME 协议在验证你对域名有控制权时有两种常见方式一种是在域名对应的 Web 服务上放一个临时文件叫 HTTP-01另一种是在域名的 DNS 解析记录里加一条随机 TXT 记录叫 DNS-01。DNS-01 这条路径天然适合 API 自动化因为加解析记录这件事可以直接通过阿里云的 OpenAPI 完成acme.sh 只需要调用一次 API剩下的等待和清理都是自动的。如果你的域名恰好在阿里云解析那这套组合就是最顺的就算你的域名在其他解析商acme.sh 也有对应的插件思路一模一样。2. 动手前的原理准备两种ACME校验方式2.1 HTTP-01校验和DNS-01校验我该选哪个在正式开始操作之前建议先花三分钟把校验原理搞清楚。ACME 协议的核心目标只有一个证明“你拥有这个域名”。CA 不会听你空口说它会要求你在某个地方放一个只有域名控制者才能放的东西然后它去检查。HTTP-01 的方式是CA 给你一个随机 token你要把这个 token 放到一个固定路径下比如http://你的域名/.well-known/acme-challenge/随机token然后 CA 通过公网访问这个 URL看到内容一致就认为你拥有该域名。这个方式实现起来最简单但要求你的服务器必须能被公网通过 80 端口访问。很多场景不满足这个条件比如家庭宽带没有公网 80 端口、服务器防火墙只放了 443、或者你压根不想为证书验证单独开启 HTTP 服务。DNS-01 的方式是CA 让你在域名的 DNS 解析记录里添加一条 TXT 记录记录名通常是_acme-challenge.example.com内容是 CA 指定的随机字符串。CA 通过查询这条 TXT 记录来判断你是否拥有域名。这种方式的好处是不依赖 80 端口、不依赖 Web 服务器只要你能控制域名的 DNS 解析就能完成验证。两种方式的对比如下对比项HTTP-01DNS-01验证原理在 Web 根目录放临时文件在 DNS 添加 TXT 记录是否需要公网 80 端口需要不需要是否支持泛域名不支持支持是否需要手动操作 DNS不需要需要配合 DNS API 自动化自动化难度低中但自动化后最省心我现在的服务器基本都是默认只开 44380 端口很多情况下压根不监听所以我几乎只用 DNS-01。再加上我的域名解析都在阿里云--dns dns_ali这个参数就是 acme.sh 专门为阿里云 DNS 准备的插件。2.2 为什么泛域名证书必须走DNS验证泛域名证书也就是证书里包含*.example.com这种通配符的证书有张“万能通行证”的爽感。你只要签一张以后blog.example.com、api.example.com、shop.example.com都能用不用每个子域名单独申请特别适合子域名多、又不想逐个维护的场景。但泛域名证书不能通过 HTTP-01 签发原因很简单你没法在“所有子域名”的 Web 根目录下都放一个对应的验证文件CA 也不知道该访问哪个具体域名。DNS-01 就没有这个问题因为通配符属于同一套 DNS 区域你只要在example.com这个区域的解析记录里加一条_acme-challenge.example.com的 TXT 记录CA 验证通过整张通配符证书就下来了。这也是为什么“泛域名”和“DNS API 自动化”是天然的一对。acme.sh 的dns_ali插件做的事就是把“添加 TXT 记录、等生效、请求 CA 验证、删除 TXT 记录”这一整套动作变成全自动。你只需要在命令行里把域名列表写完剩下的交给它。3. 阿里云侧准备RAM子用户与最小权限3.1 创建RAM子用户并生成AccessKey要让 acme.sh 能自动操作你的 DNS 解析它需要一组阿里云的 AccessKey。这里有两件事必须强调第一不要用主账号的 AccessKey第二每个子账号的权限要做到最小化。主账号的 AccessKey 权限太大一旦泄露别人不仅能改你的解析记录还能动你账号里的其他资源风险极高。正确的做法是创建一个专门的 RAM 子用户只给它 DNS 解析相关的权限。具体操作如下登录阿里云控制台进入“RAM 访问控制”页面。左侧菜单选择“身份管理 用户”点击“创建用户”。登录名称可以取acme-dns之类的名字访问方式一定要勾选“OpenAPI 调用访问”。创建成功后页面会显示 AccessKey ID 和 AccessKey SecretSecret 只显示这一次记得立刻保存到安全的地方最好存到密码管理器里。回到用户列表找到刚创建的用户进入“权限管理”准备给它授权。3.2 给AccessKey授权建议用最小权限策略在 RAM 控制台里最简单的做法是给这个子用户添加系统自带的AliyunDNSFullAccess策略。这个名字听起来很大但至少它只局限于 DNS 解析服务不会牵扯到 ECS、OSS 等其他资源。如果你不想折腾直接加这个系统策略就够了。不过我更推荐再往前一步用一个自定义的最小权限策略只允许 acme.sh 在解析记录里做“增删查”这三件事。你可以创建一个自定义权限策略策略内容如下{ Version: 1, Statement: [ { Effect: Allow, Action: [ alidns:AddDomainRecord, alidns:DeleteDomainRecord, alidns:DescribeDomainRecords ], Resource: * } ] }这段策略的意思是只允许调用阿里云 DNS 的添加解析记录、删除解析记录、查询解析记录这三个 API。acme.sh 在签发证书和续期证书时需要做的就是“添加 TXT 记录”和“验证通过后删除 TXT 记录”偶尔会查询一下记录状态。给它这三个 Action 完全够用。把这个自定义策略创建好之后在 RAM 用户详情页点击“添加权限”搜索刚才创建的策略名称授权即可。授权之后阿里云侧的准备就完成了。这里再啰嗦一句AccessKey 是敏感信息千万别提交到 Git 仓库别写进会公开的配置文件里不然等于把 DNS 的控制权拱手送人。4. acme.sh安装、配置与首次签发实操4.1 安装acme.sh并设置默认CA现在开始服务器端的操作。不管你的服务器是 Ubuntu、Debian 还是 CentOSacme.sh 的安装方式都一样。登录服务器执行curl https://get.acme.sh | sh -s email你的邮箱example.com安装脚本跑完之后会自动把 acme.sh 安装到当前用户的~/.acme.sh/目录下并把~/.acme.sh/acme.sh的别名写进你的 shell 配置文件。如果你用的是 bash执行source ~/.bashrc让别名生效然后验证一下acme.sh --version看到版本号输出说明安装成功。不同发行版可能提醒你需要安装curl或socat按提示装上即可。安装好之后我建议立刻做一件事显式设置默认 CA。acme.sh 在不同版本里默认使用的免费 CA 不完全一样有的版本默认 Let’s Encrypt有的版本默认 ZeroSSL为避免后续行为不一致直接指定acme.sh --set-default-ca --server letsencrypt为什么要这么干主要原因是我希望证书申请和续期的行为可预期。Let’s Encrypt 在业内验证时间久、兼容性好而且 acme.sh 对它的支持最成熟。指定默认 CA 之后后面所有域名都用这套标准省得出现“昨天签发好端端的今天换个 CA 出幺蛾子”的情况。4.2 配置阿里云DNS凭据并签发证书接下来配置阿里云 DNS API 的凭据。先手动导出两个环境变量让 acme.sh 知道该用哪组 AccessKeyexport Ali_Key你的AccessKey ID export Ali_Secret你的AccessKey Secret然后执行签发命令。以给example.com和它的所有一级子域名签发泛域名证书为例acme.sh --issue --dns dns_ali -d example.com -d *.example.com命令里几个参数拆开看--issue发起证书签发请求。--dns dns_ali使用 DNS 验证方式并且指定使用阿里云 DNS 插件。-d example.com把主域名加进证书。-d *.example.com把泛域名加进证书。这里要注意*.example.com并不包含example.com本身所以为了证书既能覆盖根域名又能覆盖子域名你需要在一条命令里同时写上example.com和*.example.com。首次执行时acme.sh 会调用阿里云 DNS 的 API自动添加一条_acme-challenge.example.com的 TXT 记录。日志里会出现类似Adding TXT record的提示随后它会等待一段时间让解析生效。这个等待是正常的通常几十秒不要中途按CtrlC把进程掐了。等 CA 验证完成acme.sh 会自动删除那条 TXT 记录然后开始下载新证书。签发成功后证书文件默认存放在~/.acme.sh/example.com_ecc/目录下。之所以带_ecc后缀是因为 acme.sh 默认使用 ECC 密钥也就是 ECDSA 算法生成的私钥。ECDSA 密钥更短、性能更好现代浏览器都支持直接用就行。顺便说一句第一次签发时acme.sh 会把Ali_Key和Ali_Secret保存到~/.acme.sh/account.conf文件里之后你再为其他域名签发证书就不需要重复 export 了。这个文件里存的是明文密钥务必注意权限和保管别让它曝光。4.3 首次签发时自动添加与删除TXT记录的过程观察第一次跑签发命令的时候我强烈建议你别只看结果稍微盯一眼过程。用阿里云 RAM 子用户执行签发后你可以打开阿里云控制台的“云解析 DNS”列表找到example.com这个域名刷新解析记录。你会看到一条系统自动创建的 TXT 记录记录名是_acme-challenge内容是一串随机字符串类型是 TXT。这个瞬间非常有“见证魔法”的感觉之前你手动添加解析记录时至少要登录控制台、找到域名、点击添加解析、填写记录类型、记录值、TTL现在这一切都被 API 接管了。等签发流程结束最多几分钟你再刷新一次页面那条 TXT 记录已经不在了。这就是 DNS API 自动化的价值它把原本需要人工参与的“验证 清理”环节变成了一个不可见但可靠的闭环。如果你的签发日志里出现了TXT record created和Sleep 20 seconds之类的输出都是正常节奏。如果过程中报错比如权限不足、域名不存在、记录添加失败别急着慌后面的问题排查章节会专门讲。5. 证书部署到Nginx并打通自动续期5.1 用install-cert把证书放到Nginx的稳定路径证书签发成功只是第一步更关键的是把证书“安装”到一个 Nginx 能稳定读取的位置。很多教程到这里就直接让你从~/.acme.sh/里复制文件我不建议这么做。原因有两个第一~/.acme.sh/下是 acme.sh 的内部存储目录它的文件结构在版本升级时可能变化第二Nginx 的工作进程通常以www-data或nginx用户运行它未必有权限读你 root 家目录下的私钥文件。正确做法是使用 acme.sh 自带的安装导出命令acme.sh --install-cert -d example.com \ --key-file /etc/nginx/ssl/example.com.key \ --fullchain-file /etc/nginx/ssl/example.com.pem \ --reloadcmd systemctl reload nginx先执行mkdir -p /etc/nginx/ssl把目标目录创建好再跑这条命令。这里每个参数都值得说一下-d example.com指定要安装哪个域名对应的证书。--key-file私钥文件的最终存放路径。--fullchain-file完整证书链文件的存放路径。它包含站点证书和中间证书Nginx 配置时用这个文件最省心。--reloadcmd证书安装或续期成功后要自动执行的命令。这里是systemctl reload nginx让 Nginx 重新加载证书。--reloadcmd是一个很容易被忽略但极其重要的参数。它的作用是以后每次 acme.sh 自动续期成功都会自动帮你执行一次 Nginx reload让新证书立即生效。你不需要再写额外的钩子脚本也不用担心续期了但 Nginx 还在用旧证书的问题。5.2 Nginx站点配置与HTTPS生效验证证书文件就位后修改 Nginx 站点配置。打开你的站点配置文件比如/etc/nginx/conf.d/example.com.conf写入以下内容server { listen 443 ssl http2; server_name example.com www.example.com; ssl_certificate /etc/nginx/ssl/example.com.pem; ssl_certificate_key /etc/nginx/ssl/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; location / { root /var/www/html; index index.html; } }如果你的站点原本只有 80 端口新增这段配置后别忘了检查配置语法。执行nginx -t输出syntax is ok和test is successful之后再执行 reloadsystemctl reload nginx到这里HTTPS 应该已经可以访问了。用浏览器打开https://example.com地址栏应该出现小锁图标。想从命令行确认证书信息的话可以执行echo | openssl s_client -servername example.com -connect example.com:443 2/dev/null | openssl x509 -noout -subject -issuer -dates这个命令可以看到证书的签发对象、签发机构和有效期实测下来非常方便。5.3 验证自动续期链路是否真的能跑通先说结论acme.sh 安装时会在系统 crontab 里自动注册一个定时任务每天执行一次检查。它内部有判断逻辑只有当证书距离过期不足 30 天时才会真正发起续期请求所以不用担心它每天都去打扰 CA。验证定时任务是否存在的命令crontab -l | grep acme正常情况下你会看到一行类似这样的输出13 6 * * * /root/.acme.sh/acme.sh --cron --home /root/.acme.sh /dev/null这说明 acme.sh 的自动续期已经挂上了。为了确认整条链路真的能跑通我建议你做一次手动强制续期测试。注意这个操作会真实地向 CA 申请一张新证书所以别在刚签完证书之后频繁执行Let’s Encrypt 对同一域名签发的重复证书有数量限制频繁强制续期可能触发限流。测试频率控制在“确认链路没问题就行平时不跑”。测试命令acme.sh --renew -d example.com --force观察输出如果看到续期成功再检查一下 Nginx 进程是否被 reload 过。你可以看一下 Nginx 的启动时间或直接重新访问网站确认证书日期已经更新。只要这次强制续期成功并且 Nginx 自动 reload 生效你的自动续期链路就正式闭环了。以后证书到期前cron 会自动触发续期续期成功会自动运行--reloadcmd你再也不用管了。6. 常见错误排查与我的避坑笔记6.1 高频报错与解决办法速查表用这套方案一年多我把自己遇到过的、以及帮别人排查过的高频问题整理成了下面这张表建议收藏备用。现象可能原因解决办法提示Ali_Key/Ali_Secret未设置环境变量没导出或者account.conf里不存在重新执行export Ali_Key和Ali_Secret再执行签发命令阿里云 API 返回403 ForbiddenRAM 子用户权限不足或授权未生效检查权限策略确认已授予AliyunDNSFullAccess或自定义 DNS 策略一般等一两分钟生效签发时提示域名不存在域名不在当前阿里云账号的云解析列表中确认域名已经在阿里云云解析中并且已经添加了该域名的解析记录证书签发成功但浏览器报警证书链不完整或 Nginx 只读取了站点证书而不是完整链确认 Nginx 中使用的是--fullchain-file生成的文件不要用仅包含站点证书的 crt 文件访问 HTTPS 时证书还是旧日期续期后没有触发 reload检查--install-cert时是否带了--reloadcmd没有就重新执行一次安装命令泛域名证书覆盖不了a.b.example.com通配符只匹配一级子域名*.example.com不覆盖a.b.example.com需要为更深层级单独申请证书日志里出现 CA 限流提示短时间内频繁强制续期停止手动--force续期等待 CA 限制解除日常依赖 cron 自动续期即可acme.sh 的日志文件在~/.acme.sh/acme.sh.log遇到任何看不懂的报错第一件事就是去翻日志。日志里会把 API 调用过程、CA 返回的每一步都记录下来排查效率会高很多。6.2 几个我亲手踩过、也看别人踩过的坑第一个坑只--issue不--install-cert。我最早以为只要证书签发成功Nginx 就能自动用上。结果发现证书文件确实更新了但 Nginx 配置文件里指向的还是旧的证书路径更坑的是我根本不知道它已经更新了。后来才明白--issue只是负责“签发证书”--install-cert才是负责“把证书文件复制到指定位置并执行 reload 命令”。这两步缺一不可只做第一步的话你的自动续期只是“自动申请”不是“自动部署”。第二个坑把私钥文件放在家目录里Nginx 读不了。有一次换服务器我图省事直接用~/.acme.sh/*.key路径写进 Nginx 配置结果 Nginx reload 时一直报权限错误。后来进了/etc/nginx/ssl目录还要注意目录权限。Nginx 主进程通常是 root 启动但 worker 进程会降权到nginx或www-data用户私钥文件的权限建议设置为640属组设置为 Nginx 运行用户比如chown -R root:nginx /etc/nginx/ssl chmod 640 /etc/nginx/ssl/*这样既保证 Nginx 能读取又避免其他无关用户拿到私钥内容。第三个坑泛域名证书的“泛”的范围理解偏差。*.example.com确实能覆盖www.example.com、api.example.com、blog.example.com但它覆盖不了dev.api.example.com。如果你有这种多级子域名的需求老老实实把多级域名作为一个独立域名去签或者评估一下是不是真的需要那么深的子域名层级。第四个坑把密钥当成普通配置乱放。acme.sh 会把阿里云 AccessKey 明文存在~/.acme.sh/account.conf里这本身没问题但如果你习惯把这台服务器的家目录打包备份到公开仓库、或者截图发到群里求助那密钥就泄了。阿里云控制台里一旦发现 AccessKey 泄露第一时间去 RAM 里删掉并重新生成别犹豫。6.3 多域名环境下的批量签发小技巧如果你跟我一样手里不止一个域名一条条执行--issue会有点笨。这里分享一个小技巧把域名列表写进一个数组用 for 循环批量签发和安装。比如domains( example.com blog.example.com another.com ) for domain in ${domains[]}; do acme.sh --issue --dns dns_ali -d $domain --server letsencrypt acme.sh --install-cert -d $domain \ --key-file /etc/nginx/ssl/${domain}.key \ --fullchain-file /etc/nginx/ssl/${domain}.pem \ --reloadcmd systemctl reload nginx done这个脚本只适合处理普通域名泛域名需要单独写成-d example.com -d *.example.com所以不要完全无脑套用。但它能很直观地说明一件事一旦走通自动化加域名这件事就从“繁琐操作”变成了“往数组里加一行字符串”。这套流程我用到现在已经快两年了。坦白说中间最值钱的经验不是某条具体命令而是把“人”从循环里拿掉——证书续期这种事人的记性是最不可靠的组件。最后再提一个建议如果你机器上还有别的域名别一个个手敲把上面的 for 循环脚本改一改新增一个站点整个流程不超过一分钟。这才是这套方案真正让人上瘾的地方。