ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

acme.sh+阿里云DNS实现SSL证书全自动续期实战指南

acme.sh+阿里云DNS实现SSL证书全自动续期实战指南 如果你还在靠日历提醒自己“该续SSL证书了”那说明你还没被证书过期坑过或者坑得还不够狠。我入行这几年见过凌晨三点被用户截图砸醒的运维也见过因为证书过期被浏览器拦在站点外面、业务直接停摆的团队。后来我把acme.sh和阿里云DNS API接到一起做了全自动的HTTPS证书管理证书从签发到续期全程不需要人碰。今天就把这套方案从原理到实施完完整整写出来包括中间踩过的坑和排查思路。想省心的人照着做基本能一次跑通。1. 证书过期那天的深夜救火手动续期的真实成本1.1 一次证书过期引发的连锁事故先说说最直观的损失。证书一旦过期浏览器不会像网站挂了那样给个清晰的“无法访问”而是弹出一整页红屏警告“您的连接不是私密连接”。用户第一反应是怀疑网站被黑紧接着直接关掉页面如果是小程序或App后端请求会成片失败。比这个更隐蔽的是有些服务端对TLS握手失败处理得不够优雅连接池会被大量半开连接占满整站看起来就像宕机了一样。最麻烦的地方在于证书过期永远发生在你不想处理的时间段周末、深夜、大促当天。我见过一个团队在双十一前夜被监控报警砸醒原因就是证书比预期早了两天失效而负责的人还在休假。那次事故之后他们才下定决心把证书管理自动化。1.2 手动续期流程拆解光“记着”这一步就够烦了手动续期SSL证书的完整流程远比大多数人想象得繁琐先登录证书管理平台确认哪张证书快到期了生成CSR或者直接在线申请新证书验证域名所有权常见的是HTTP文件验证或者DNS TXT记录验证等待CA证书颁发机构签发下载证书文件把证书和私钥上传到服务器指定目录修改Nginx或Apache配置里的证书路径执行reload最后再用浏览器或命令行验证一遍。这个流程里最容易出错的就是域名验证那一步。HTTP验证需要把临时文件放到网站根目录可很多服务器上不只有一个站点CDN缓存、防盗链规则、反向代理路径一叠加验证请求很可能被挡掉DNS验证需要去DNS控制台手动加TXT记录等生效再等CA查询记录一不留神就写错。这些步骤重复做上十次总有几次会出岔子。1.3 为什么很多人明知有自动方案却一直没动手不是大家不知道有acme.sh这类工具而是存在几个常见的心理障碍怕配置错觉得DNS API很危险不敢碰或者认为自动续期需要额外花钱买服务。实际上acme.sh是免费开源的配合阿里云DNS API也完全不需要购买证书产品只需要在阿里云RAM里创建一个受限权限的子账号就行。还有一部分人试过一次没跑通比如环境变量写错了、TXT记录冲突失败后就直接放弃退回手动续期的老路。这个我太理解了因为我自己第一次配置时也踩过类似的坑。但回过头看真正把原理搞明白之后整条链路一小时之内就能搭完之后证书会一直自己续期再也不用操心。2. ACME协议与DNS API验证自动续期背后的两个关键机制2.1 Lets Encrypt与ACME协议免费证书的发放逻辑要理解自动续期先得知道现代免费证书是怎么发出来的。ACME协议是CA和客户端之间的自动化协议客户端生成密钥对、发起申请、完成域名验证、下载证书全部可以通过API完成。Lets Encrypt是这个协议最知名的践行者它把证书有效期设计成90天背后逻辑就是逼着所有人把续期自动化。90天这个周期很有意思说长不长说短不短。手动续期的人经常忘记等到浏览器开始报警才想起来。而ACME协议配合自动化脚本恰恰能把这个周期管理得很好——每天检查一次临近30天时自动发起续期根本不需要人记住哪个域名、哪张证书、哪一天到期。2.2 域名验证的两种方式HTTP-01和DNS-01ACME协议里有两种最常见的域名所有权验证方式HTTP-01和DNS-01。它们的核心区别是验证的对象不一样。HTTP-01验证要求CA能通过公网访问到你的服务器请求一个特定路径下的临时文件文件内容由ACME客户端生成并放置。DNS-01验证则是要求你在域名的DNS记录里添加一条TXT记录内容也是ACME客户端生成的随机值CA通过查询TXT记录来确认你对域名有控制权。我用过之后的体会是HTTP-01部署起来简单但限制很多。服务器必须在公网可访问80端口不能被防火墙挡住CDN和反代配置得正确而且它没法签发泛域名证书。DNS-01天然没有这些限制它对“服务器是否开放”完全不关心只关心你能不能往DNS里加记录。下面这个表格可以直观对照对比项HTTP-01DNS-01验证原理CA访问服务器上的临时文件CA查询域名TXT记录是否需要公网开放需要通常要求80端口可访问不需要任意机器可执行是否支持泛域名不支持支持自动化难度依赖Web服务正常运行依赖DNS服务商API常见使用场景单服务器、手动配置自动化、泛域名、无公网内网机器2.3 acme.sh为什么选DNS API自动写TXT记录再自动删把DNS-01进一步自动化就需要DNS服务商的API。acme.sh做的事情就是读取你配置好的API密钥帮你自动调用阿里云云解析DNS的接口签发前添加一条_acme-challenge的TXT记录等CA验证完成后再自动把这条TXT记录删掉。这一步是整条链路里最关键的“无人值守”能力。如果靠手动去控制台贴TXT记录就算只做一次也会觉得麻烦更别提每90天来一次。而用API自动增删记录整个过程只需要在首次配置时提供AccessKey之后每次续期都由脚本在后台自主完成。acme.sh的dns_ali插件就是专门对接阿里云DNS API的逻辑封装得已经非常成熟我们只需要把参数配对。3. 阿里云侧的准备RAM账号、权限策略与AccessKey安全边界3.1 别拿主账号AccessKey开刀RAM子账号怎么建阿里云控制台创建AccessKey时有主账号AccessKey和RAM用户AccessKey两种选择。很多老教程图省事直接让用户用主账号我强烈不建议。主账号AccessKey拥有账号下所有资源的权限一旦泄露你的ECS、OSS、数据库全完了。哪怕只是放在服务器环境变量里也等于给攻击者留了一把万能钥匙。正确做法是创建一个专用的RAM子账号只授权云解析DNS的读写权限。具体步骤是登录阿里云控制台搜索“RAM访问控制”进入“身份管理”里的“用户”点击“创建用户”。登录名称建议起成acme-dns-bot这种一看就知道用途的名字访问方式勾选“OpenAPI调用访问”这样系统才会生成AccessKey ID和AccessKey Secret。3.2 授权AliyunDNSFullAccess权限范围说明创建完RAM用户后下一步是授权。在用户详情页点击“添加权限”搜索“AliyunDNSFullAccess”确认添加即可。这是一个系统策略包含云解析DNS的读取、写入、删除等全部操作。acme.sh的dns_ali插件在签发流程中需要做两件核心操作添加TXT记录和删除TXT记录这些权限都包含在AliyunDNSFullAccess里。如果你的团队对权限管控非常严格也可以不用系统策略而是自定义一个最小权限策略只包含alidns:AddZoneRecord、alidns:DeleteSubDomainRecords、alidns:DescribeDomainRecords这几个API。效果一样但更符合最小权限原则。3.3 AccessKey的保存与安全使用建议AccessKey创建成功的那一刻页面上会显示AccessKey ID和AccessKey SecretSecret只会出现这一次之后再也查不到。我习惯先把它们复制到密码管理器里再在服务器上单独建一个文件保存比如/root/.aliyun-key然后执行chmod 600把权限收紧。还有一个容易忽略的细节不要在命令行历史里留下AccessKey。直接在交互shell里export会让密钥写入~/.bash_history有泄露风险。更稳妥的方式是每次执行签发命令前用source命令加载密钥文件或者直接在前面提到的密钥文件里写好几行export需要时source一下。4. acme.sh实战签发第一张真实的HTTPS证书4.1 安装acme.sh并完成基础设置安装acme.sh只需要一条命令curl https://get.acme.sh | sh -s emailyour_emailexample.com这条命令会从GitHub拉取脚本并安装到当前用户的~/.acme.sh目录下。安装完成后执行source ~/.bashrc然后运行acme.sh --version确认可用。实际可执行文件就在~/.acme.sh/acme.sh日常可以直接用acme.sh这个别名。在签发证书之前强烈建议先把默认CA固定下来。新版acme.sh的默认CA可能是ZeroSSLZeroSSL需要先注册邮箱有时候没注册会直接报错而Lets Encrypt在大多数场景下更稳定也免去额外注册步骤。切换命令很简单acme.sh --set-default-ca --server letsencrypt4.2 写入阿里云API密钥并触发DNS验证接下来配置阿里云DNS API的密钥。这里要特别注意环境变量名是Ali_Key和Ali_Secret不是Aliyun_Key。我第一次配的时候照着旧笔记写错报了挺奇怪的错排查了半天才发现是变量名不对。export Ali_Key你的AccessKeyId export Ali_Secret你的AccessKeySecret执行完export后acme.sh会把配置保存到~/.acme.sh/account.conf以后自动续期时同样会读取不需要每次签发前都手动导出。接着就可以发起第一次签发了。比如要给example.com以及它的泛域名*.example.com同时签发一张证书命令是这样的acme.sh --issue --dns dns_ali --dnssleep 120 -d example.com -d *.example.com这里参数--dns dns_ali告诉acme.sh使用阿里云DNS API方式验证--dnssleep 120表示添加TXT记录后等待120秒再继续给足DNS生效时间。阿里云的TXT记录通常几秒就生效了但设个120秒心里更稳。4.3 单域名、多域名和泛域名证书命令差异与适用场景如果你是第一次接触可能会纠结到底签单域名还是泛域名。我的建议很简单只有一个站点就签单域名证书命令里只写一个-d有多个子域名比如www、api、m可以放在同一张证书里用多个-d参数就行如果以后会不停加子域名直接签泛域名证书最省事一张*.example.com覆盖所有一级子域名。泛域名证书只能用DNS方式验证这也是为什么很多人宁可多花几分钟配置阿里云API也不愿每次手动加TXT记录。签发成功后acme.sh会在~/.acme.sh目录下保存证书目录名类似example.com_ecc或example.com具体取决于密钥算法。4.4 证书生成后的文件含义与目录约定证书签发完成后在~/.acme.sh对应域名目录下会看到几个文件。理解它们的含义很重要fullchain.cer完整的证书链包含服务器证书和中间证书这是Nginx的ssl_certificate需要的example.com.key私钥文件Nginx的ssl_certificate_key用它ca.cer中间证书一般不需要直接配置cert.cer只有服务器证书没有中间证书不能单独用于生产环境。很多新手踩过的坑就是把cert.cer当成完整证书配置进Nginx结果浏览器报“证书链不完整”。正确做法是始终使用fullchain.cer。另外不建议在Nginx配置里直接引用~/.acme.sh目录下的文件这个目录是acme.sh的内部存储后续脚本更新、目录变动都可能影响线上服务。更规范的做法是用接下来要说的--install-cert把证书复制到固定路径。5. 自动续期与Nginx无缝衔接让证书自己滚动更新5.1 acme.sh的定时任务到底怎么工作的安装acme.sh时它会自动往当前用户的crontab里写入一条定时任务默认每天执行一次acme.sh --cron。执行crontab -l应该能看到类似这样的内容0 0 * * * /root/.acme.sh/acme.sh --cron --home /root/.acme.sh /dev/null但注意这个定时任务不是每天重新签发证书。acme.sh会比较每张证书的剩余有效期只有到期前30天以内才会触发续期。也就是说如果证书刚签发之后一个月内的cron都会安静地跳过。我见过有人担心cron被清理专门在/var/spool/cron里手写任务其实没必要。acme.sh安装时自动加的这条crontab已经足够可靠只要系统没被人为禁用cron自动续期就一直会跑。5.2 用--install-cert固定证书位置别让Nginx找不到现在要把证书安装到Nginx能读取的固定路径同时把自动续期后的动作提前绑定好。先创建目录mkdir -p /etc/nginx/ssl然后执行安装命令acme.sh --install-cert -d example.com \ --key-file /etc/nginx/ssl/example.com.key \ --fullchain-file /etc/nginx/ssl/example.com.cer \ --reloadcmd systemctl reload nginx这里--install-cert并不是“安装一次就完事”。它会把这套安装参数保存到~/.acme.sh/example.com/example.com.conf以后每次自动续期成功后acme.sh都会按照这个配置重新复制证书文件并且执行--reloadcmd里的命令。换句话说只要配好这一次以后证书更新、复制、Nginx重载全部自动完成。5.3 reloadcmd的正确写法证书更新后自动重载reloadcmd是整个自动续期闭环里不容忽视的一环。证书文件更新后Nginx不会自动感知必须reload才能让新证书真正生效。如果省略这个参数续期脚本跑完只是把新证书写到了磁盘线上进程还在用旧证书等于白续。reload命令建议用systemctl reload nginx或者nginx -s reload而不是restart。reload是平滑重载不中断活跃连接restart会短暂断开所有连接生产环境风险大。如果你的Nginx不是通过systemd管理的可以改成nginx -s reload。重点是让这条命令在续期成功后能被自动执行且当前用户有权限执行。5.4 如何验证自动续期链路真的通了配置完成之后最怕的就是感觉“应该没问题”实际却没跑通。验证自动续期链路最直接的办法是强制触发一次续期acme.sh --renew-all --force这条命令会忽略证书剩余天数强制把acme.sh管理的所有证书重新签发一遍。执行过程中会再次走完整的DNS API验证流程正好可以确认阿里云密钥、TXT记录增删、证书复制、Nginx reload这一整条链路是否正常。跑完后检查证书的实际过期时间openssl x509 -enddate -noout -in /etc/nginx/ssl/example.com.cer如果Not After日期往后推了约90天说明自动续期链路是通的。之后再配合crontab -l确认定时任务存在就能放心让它长期跑了。6. Nginx配置HTTPS从裸奔到全站加密6.1 最小可用的SSL server块配置证书就位后Nginx配置是最后一公里。给一个最小可用的HTTPS配置示例server { listen 80; server_name example.com www.example.com; return 301 https://$host$request_uri; } server { listen 443 ssl; http2 on; server_name example.com www.example.com; ssl_certificate /etc/nginx/ssl/example.com.cer; 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; } }第一段server块负责把HTTP请求301跳转到HTTPS第二段server块才是真正的HTTPS站点。配置里ssl_certificate指向的必须是fullchain.cer这一点再强调一次只配服务器证书会导致部分浏览器报证书链不完整。配置完执行nginx -t检查语法确认无误后systemctl reload nginx。6.2 强制HTTP跳转HTTPS的几个细节在80端口配置里我用的是return 301而不是rewrite。return 301性能更好语义也更清晰。如果站点涉及国际化或者CDN回源可能需要按具体场景调整但对绝大多数中文站点直接301到HTTPS就够了。还有一个容易被忽略的问题如果页面里有图片、脚本、样式用了http://的绝对地址浏览器会报混合内容并拦截不安全资源。上线前最好全局扫一遍站内链接把http://改为相对路径或者//协议否则看着地址栏是https实际体验却很糟糕。6.3 证书链完整性和浏览器信任测试配置完成后可以用命令行快速验证证书链是否正常openssl s_client -connect example.com:443 -servername example.com /dev/null重点关注输出中的verify return code: 0 (ok)。这个结果说明证书链完整、域名匹配、信任链被系统认可。如果看到verify error多半是中间证书缺失或证书链顺序不对去检查全链文件是否用的是fullchain.cer。浏览器层面的检查也不能省。用无痕模式打开站点点击地址栏小锁图标确认证书状态为“有效”。还可以用一些在线的SSL检测工具从外部扫描一遍看看证书链、协议版本、弱加密套件有没有警告。7. 我踩过的坑与完整排查链路7.1 阿里云DNS API权限不足的报错长什么样第一个高频坑是RAM权限不足。如果你用阿里云AccessKey调用API时报403先别怀疑acme.sh优先检查RAM用户是否已经授权AliyunDNSFullAccess。错误信息里往往不会直接写“权限不足”可能是一串OpenAPI错误码比如InvalidAccessKeyId.NotFound、Forbidden容易让人误以为密钥写错了。排查链路是先用阿里云控制台确认AccessKey状态是“启用”再到RAM用户权限管理里确认AliyunDNSFullAccess已添加。策略授权有时需要等一两分钟才能完全生效刚配完就立刻跑命令偶尔会失败稍等再试就行。7.2 TXT记录残留与根域名解析冲突第二个坑和DNS记录本身有关。如果你是第一次使用DNS方式验证之前又手动添加过_acme-challenge.example.com的TXT记录acme.sh自动添加时可能因为记录已存在而失败或者验证时读到的是旧记录。遇到这类问题先去云解析DNS控制台把旧的_acme-challenge记录手动删干净再重新执行签发命令。还有一种情况是TXT记录已经生效但CA那边还没查到这时候适当调大--dnssleep的值比如从120改成180甚至300能有效减少偶发失败。7.3 续期失败却“不报错”的静默风险排查路径这条值得单独写一段acme.sh的cron任务默认把输出重定向到/dev/null意味着续期失败时你可能收不到任何提示。最危险的不是失败而是失败得无声无息。我的习惯是定期主动检查最简单的方式是每月看一次证书到期时间openssl x509 -enddate -noout -in /etc/nginx/ssl/example.com.cer如果发现到期时间没更新再去看acme.sh的日志和调试信息。tail -n 50 ~/.acme.sh/acme.sh.log acme.sh --renew-all --debug 2--debug 2会输出完整的API调用和HTTP交互过程基本能把问题定位到具体环节密钥失效、DNS添加失败、CA接口超时或者只是网络抖动。7.4 多服务器、多域名场景下的管理习惯最后说点复杂场景下的体会。如果你有多台服务器不建议每台都装acme.sh分别签发证书那样每台机器都要保存阿里云API密钥暴露面会变大。更稳妥的做法是只在一台主服务器上跑acme.sh证书续期后通过rsync或配置管理工具把/etc/nginx/ssl目录同步到其他机器同步完成后再执行reload。多域名的情况则可以把所有域名放进同一张证书里签发比如-d example.com -d www.example.com -d api.example.com。这样管理起来很集中acme.sh --list能直接看到所有证书的状态。只要域名都在阿里云DNS解析下一张命令就能覆盖完。7.5 CA选择的补充建议如果Lets Encrypt接口在你的网络环境下不太稳定acme.sh支持切换默认CA比如ZeroSSL。切换命令是acme.sh --set-default-ca --server zerossl acme.sh --register-account -m your_emailexample.com --server zerosslZeroSSL注册账号后签发流程和Lets Encrypt基本一致acme.sh会屏蔽掉底层的协议差异。我个人还是更倾向Lets Encrypt作为首选ZeroSSL作为备用。切换后建议强制续期一次验证链路确认没问题再离开。这套方案跑通之后唯一需要你上心的就是确保服务器cron没被人为禁用、证书目录还有磁盘空间。至于证书本身它会自己照顾好自己。我在一台很老的个人服务器上配好以后两年多没再打开证书管理页面每次看到Chrome小锁图标都是绿的那种感觉才是自动化该有的样子。
返回列表