acme.sh + 阿里云DNS + Nginx:自动化SSL证书管理实战指南
1. 项目缘起:为什么我放弃了付费证书和手动申请
做Web服务,给网站套上HTTPS的锁头,现在已经是基本操作了。早些年,我还会去各大云服务商那里买付费的泛域名证书,一年大几百甚至上千,图个省心。后来Let‘s Encrypt横空出世,免费SSL证书成了标配,但每次申请、部署、续期,那一套certbot命令敲下来,再手动改Nginx配置,还是觉得麻烦,尤其是域名多的时候。
直到我遇到了acme.sh。这工具彻底改变了我的证书管理方式。它把申请、验证、部署、续期这一整套流程全自动化了,而且是纯Shell脚本写的,轻量到几乎可以在任何Linux环境跑起来。最让我心动的是它对泛域名(通配符证书,*.example.com)的支持,配合阿里云DNS的API,可以实现完全无人值守的自动化。
所以,今天这篇内容,就是把我这几年在Debian服务器上,用acme.sh+ 阿里云DNS + Nginx这套组合拳的实战经验,毫无保留地拆解给你看。这不是一个简单的命令罗列,我会带你走一遍完整的流程,并重点分享那些官方文档里不会写,但实际部署中一定会遇到的“坑”和应对技巧。无论你是管理着十几个服务的运维,还是只有一个个人博客的开发者,这套方案都能让你从此和证书过期告警说再见。
2. 核心工具链选型:为什么是acme.sh + Aliyun DNS + Nginx?
在开始动手之前,我们得先搞清楚为什么选这三样东西组合在一起。市面上类似的方案不少,比如certbot官方工具,或者其它ACME客户端。我的选择是基于下面几个实实在在的考量:
2.1 为什么选择acme.sh而非certbot?
certbot是EFF(电子前沿基金会)的亲儿子,知名度最高。但它有几个点让我最终转向了acme.sh:
- 零依赖与轻量:
acme.sh就是一个Shell脚本,安装后除了系统自带的curl、cron等,几乎不引入额外依赖。而certbot通常需要Python环境和一堆包,在最小化安装的系统上可能更折腾。 - 配置的持久化与独立性:
acme.sh会把你的账户信息、API密钥等配置,以及生成的证书,都放在用户主目录下的.acme.sh/文件夹里。这个设计非常“Unix哲学”,所有东西都在一个地方,备份和迁移极其简单。certbot的配置则分散在/etc/letsencrypt/等系统目录,权限管理上有时会更复杂。 - 更灵活的DNS API支持:对于泛域名证书,DNS验证是唯一的方式(因为HTTP文件验证无法验证
*.example.com这样的通配符)。acme.sh原生支持超过50种DNS服务商的API,包括国内最常用的阿里云、腾讯云、Cloudflare等,而且集成方式非常统一。虽然certbot也可以通过插件实现,但acme.sh的集成感觉更原生、更稳定。 - 自动续期与通知:
acme.sh在安装时会自动创建一个cron任务(或systemd timer),每天检查证书是否快过期(默认是到期前30天),并自动续期。续期成功后,它还能通过配置的钩子脚本(比如reloadcmd)自动重启Nginx,真正实现“一次配置,永久有效”。这个自动化闭环做得非常彻底。
2.2 为什么选择阿里云DNS进行验证?
对于在中国大陆有业务的网站,域名解析服务很多都放在阿里云(万网)或腾讯云。选择阿里云DNS API进行验证,原因很直接:
- 延迟与可靠性:DNS验证需要向你的域名DNS服务商添加一条特定的TXT记录。使用阿里云自己的API,在阿里云服务器上操作,解析和生效速度最快,避免了跨网络、跨服务商可能带来的超时或失败。
- API成熟稳定:阿里云的DNS API文档齐全,功能稳定,访问控制策略(RAM)配置清晰,安全性有保障。
- 无缝集成:
acme.sh对阿里云DNS的支持是“一等公民”级别的,只需要提供两个密钥,剩下的验证、添加记录、删除记录全自动完成,你完全不用关心DNS记录是怎么被操作的。
2.3 为什么是Nginx?
这几乎是事实标准了。Nginx作为Web服务器和反向代理的普及率极高,性能优秀,配置相对清晰。acme.sh对Nginx的支持也非常好,不仅可以自动安装证书,还能在续期后自动重载配置,确保服务不间断。我们的操作将主要围绕Nginx的配置文件展开。
3. 前期准备:阿里云RAM访问控制配置详解
这是整个流程中最关键,也最容易出错的一步。我们不能直接使用主账户的AccessKey,那相当于把家门钥匙扔在外面。必须创建一个最小权限的子用户(RAM用户),只赋予它管理指定域名DNS记录的权限。
3.1 创建RAM用户并获取AccessKey
- 登录阿里云控制台,进入访问控制(RAM)页面。
- 在左侧菜单选择用户,点击创建用户。
- 输入一个用户名,例如
acme-dns-operator。“控制台访问”务必选择“禁用”,因为这个用户只需要API调用权限,不需要登录控制台。点击“确定”。 - 创建成功后,系统会显示AccessKey ID和AccessKey Secret。这是唯一一次完整显示Secret的机会,请务必立即保存到安全的地方(如本地密码管理器)。如果丢失,只能创建新的AccessKey。
3.2 为RAM用户授权精细化的DNS策略
接下来,我们需要创建一个自定义策略,精确控制这个RAM用户只能操作我们指定域名的DNS记录。
- 在RAM控制台,进入权限管理 > 权限策略,点击创建权限策略。
- 选择脚本编辑模式(这样权限最清晰)。
- 在策略文档中,填入以下内容。这里以你的域名是
yourdomain.com为例:
{ "Version": "1", "Statement": [ { "Effect": "Allow", "Action": [ "alidns:DescribeDomains", "alidns:DescribeDomainRecords", "alidns:AddDomainRecord", "alidns:DeleteDomainRecord", "alidns:UpdateDomainRecord" ], "Resource": "acs:alidns:*:*:domain/yourdomain.com" }, { "Effect": "Allow", "Action": [ "alidns:DescribeDomains" ], "Resource": "*" } ] }让我解释一下这个策略的每一部分:
- 第一个Statement:核心权限。允许对资源
acs:alidns:*:*:domain/yourdomain.com(即你的具体域名)执行一系列DNS记录操作。包括查看域名和记录、添加、删除、更新记录。这正好覆盖了acme.sh验证所需的所有动作:添加_acme-challengeTXT记录,并在验证完成后删除它。 - 第二个Statement:辅助权限。允许执行
DescribeDomains动作,且资源为*(所有域名)。这是必须的,因为acme.sh在初始阶段需要调用这个API来获取你的域名列表,确认域名存在于你的账户下。但这个权限仅限于“查看”,不能修改其他域名。
注意:权限粒度的把握。网上有些教程会直接授予
alidns:*(所有DNS权限)或者*(所有权限),这是极其危险的做法。遵循最小权限原则,能有效避免因密钥泄露或脚本异常导致整个云账户DNS被清空等灾难性后果。
- 给这个策略起个名字,例如
AcmeDNSManagePolicy,然后创建。 - 回到用户页面,找到你创建的
acme-dns-operator用户,点击添加权限。 - 在“授权范围”选择整个云账号,在权限策略名称中搜索并选中刚刚创建的
AcmeDNSManagePolicy,完成授权。
至此,阿里云端的准备工作就完成了。你手头应该有了:
AccessKey IDAccessKey Secret- 一个仅能管理
yourdomain.com域名DNS记录的RAM用户
4. 服务器端部署:安装acme.sh与申请首张泛域名证书
现在,我们登录到你的Debian服务器。假设你使用的是非root用户(例如debian),但拥有sudo权限。
4.1 安装acme.sh
安装过程简单到令人发指。官方推荐的方式是直接通过curl下载脚本并安装。它会自动安装到你的用户主目录下,并为你创建cron任务。
# 切换到你的用户主目录,或者任意你有写权限的目录 cd ~ # 下载安装脚本并执行。注意,这里不需要sudo,它以当前用户身份安装。 curl https://get.acme.sh | sh -s email=your-email@example.com安装命令中的your-email@example.com非常重要,这是你在Let‘s Encrypt的注册邮箱,用于接收证书过期提醒等通知(虽然基本用不上,因为会自动续期),但这是ACME协议的要求。
安装完成后,脚本会自动为你做以下几件事:
- 在
~/.acme.sh/目录下克隆完整的项目。 - 创建一个别名
acme.sh,方便你调用。但需要重新加载一下Shell配置才能生效。 - 自动添加一个每日执行的cron任务(或systemd timer),用于检查并续期证书。
为了让acme.sh命令立即可用,执行:
source ~/.bashrc # 或者如果你用的是zsh: source ~/.zshrc现在,运行acme.sh --version应该能正确显示版本信息。
4.2 配置阿里云DNS API密钥
我们需要把上一步获取的AccessKey告诉acme.sh。它提供了多种方式,最安全方便的是设置成环境变量。我们将它们设置为当前Shell会话的环境变量,这样在接下来的命令中就能直接使用。
export Ali_Key="你的AccessKey ID" export Ali_Secret="你的AccessKey Secret"重要提示:这种方式设置的变量只在当前终端会话有效。为了避免每次申请证书都要重新输入,更常见的做法是将这两个变量写入你的Shell配置文件(如~/.bashrc或~/.profile)的末尾。但请注意,这会让你密钥以明文形式存储在文件中。你可以权衡便利性与安全性。对于个人服务器,通常可以接受;对于生产环境,可以考虑使用其他更安全的密钥管理方式,或者每次手动导出。
4.3 签发你的第一张泛域名SSL证书
激动人心的时刻到了。我们将使用DNS验证方式,申请一张覆盖*.yourdomain.com和yourdomain.com(根域名)的证书。
acme.sh --issue --dns dns_ali -d "*.yourdomain.com" -d "yourdomain.com"逐参数解释:
--issue:发起证书签发请求。--dns dns_ali:指定使用DNS验证方式,并且是阿里云的DNS(dns_ali是acme.sh内置的阿里云插件标识)。-d "*.yourdomain.com":指定通配符域名。-d "yourdomain.com":同时指定根域名。这样一张证书就同时保护了所有子域名和主域名。
执行这个命令后,acme.sh会开始它的魔法:
- 它会利用你提供的
Ali_Key和Ali_Secret,调用阿里云DNS API,在你的域名yourdomain.com下,自动添加一条名为_acme-challenge.yourdomain.com的TXT记录,记录值是一串随机字符串。 - 然后,它会等待一段时间(通常几十秒),并反复查询DNS,直到确认这条TXT记录在全球DNS中生效。
- DNS验证通过后,它会向Let‘s Encrypt的服务器发送验证成功的信号。
- Let‘s Encrypt确认后,就会签发证书。
acme.sh会将证书文件下载并保存到~/.acme.sh/*.yourdomain.com/目录下。 - 最关键的一步:脚本会自动调用阿里云API,删除刚才添加的那条临时的TXT记录。整个过程中,你完全不需要手动去操作DNS控制台。
如果一切顺利,你会看到绿色的 “Cert success.” 字样,恭喜你,证书已经签发成功了!证书文件就躺在~/.acme.sh/*.yourdomain.com/目录里。主要的文件是:
fullchain.cer:证书链文件(你的证书+中间CA证书),Nginx配置中ssl_certificate指令需要的就是它。*.yourdomain.com.key:私钥文件,Nginx配置中ssl_certificate_key指令需要它。
5. 证书部署与Nginx配置自动化
证书生成了,但还在用户目录下。我们需要把它放到一个Nginx能访问的标准位置(比如/etc/ssl/),并配置Nginx使用它。acme.sh的--install-cert命令可以一键完成这个操作。
5.1 创建标准的证书存储目录
首先,我们创建一个目录来存放证书。通常系统级的SSL证书放在/etc/ssl/或/etc/nginx/ssl/下。这里我习惯用后者,因为更清晰。
# 使用sudo创建目录,因为Nginx进程需要能读取这些文件 sudo mkdir -p /etc/nginx/ssl/yourdomain.com5.2 使用acme.sh自动安装证书到指定位置
现在,运行安装命令。这个命令不仅会复制证书文件,还会为你设置一个自动续期后的钩子。
acme.sh --install-cert -d "*.yourdomain.com" \ --key-file /etc/nginx/ssl/yourdomain.com/privkey.pem \ --fullchain-file /etc/nginx/ssl/yourdomain.com/fullchain.pem \ --reloadcmd "sudo systemctl reload nginx"参数详解:
--install-cert -d “*.yourdomain.com”:指定要为哪个域名安装证书。--key-file:指定私钥文件的目标路径和名称。这里我命名为privkey.pem,清晰易懂。--fullchain-file:指定证书链文件的目标路径和名称。命名为fullchain.pem。--reloadcmd:这是精华所在!指定一个在证书成功续期后要执行的命令。我们这里填的是sudo systemctl reload nginx。reload是平滑重载配置,不会中断当前连接,是更新证书的最佳方式。
执行这个命令后,acme.sh会:
- 将
~/.acme.sh/*.yourdomain.com/下的私钥和证书链文件,复制到你指定的/etc/nginx/ssl/yourdomain.com/目录。 - 在它的配置里记录下这个“安装”信息。以后当自动续期触发并成功获取新证书后,它会自动将新证书复制到同样的位置,然后执行你定义的
reloadcmd命令,重启Nginx。至此,全自动化闭环完成。
5.3 配置Nginx使用新证书
现在,我们需要修改Nginx的站点配置文件,指向我们新的证书文件。假设你的站点配置文件是/etc/nginx/sites-available/yourdomain.com。
打开该文件,找到或添加监听443端口的server块,关键配置如下:
server { listen 443 ssl http2; # 启用http2是个好习惯 listen [::]:443 ssl http2; server_name yourdomain.com www.yourdomain.com; # 指向acme.sh安装的证书 ssl_certificate /etc/nginx/ssl/yourdomain.com/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/yourdomain.com/privkey.pem; # 下面是一些推荐的SSL优化配置 ssl_protocols TLSv1.2 TLSv1.3; # 禁用不安全的旧协议 ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:...; # 使用安全的加密套件 ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; # ... 你的其他配置,如root目录、proxy_pass等 ... } # 别忘了配置HTTP到HTTPS的重定向 server { listen 80; listen [::]:80; server_name yourdomain.com www.yourdomain.com; return 301 https://$server_name$request_uri; }配置完成后,检查Nginx配置语法是否正确:
sudo nginx -t如果显示syntax is ok和test is successful,就可以重载Nginx了:
sudo systemctl reload nginx现在,用浏览器访问https://yourdomain.com,你应该能看到绿色的安全锁标志了。子域名https://api.yourdomain.com或https://blog.yourdomain.com等,也同样受到保护。
6. 进阶配置与深度踩坑指南
基础流程走通了,但要想在生产环境用得踏实,下面这些进阶知识和踩过的坑,你必须了解。
6.1 证书的存储结构与更新机制
理解acme.sh如何管理证书非常重要。在~/.acme.sh/目录下,每个域名都有一个独立的文件夹(如*.yourdomain.com)。这个文件夹里存放着:
- 从CA获取的原始证书文件(
.cer,.key,.csr等)。 - 一个名为
*.yourdomain.com.conf的配置文件。这个文件是核心,它记录了该域名的所有配置:使用的验证方式(dns_ali)、API密钥(已混淆)、证书安装路径(Le_KeyPath,Le_CertPath)以及最重要的Le_ReloadCmd(就是--reloadcmd指定的命令)。
当你运行acme.sh --install-cert时,它主要就是在更新这个配置文件,并执行一次文件复制。而每日的cron任务,会遍历所有这样的配置,检查证书有效期,如果需要续期,就重新走一遍签发流程,然后根据配置里的Le_ReloadCmd执行重载命令。
6.2 如何为多个域名或泛域名配置?
如果你有多个主域名(例如domain-a.com和domain-b.com),只需要对每个域名重复第4章和第5章的过程即可。acme.sh会为每个域名创建独立的配置和cron任务。
如果你有一个域名下有多个不同的泛域名需求(虽然不常见),例如*.app.domain.com和*.api.domain.com,你需要为每个不同的通配符前缀单独申请证书,因为*.domain.com无法覆盖二级子域名。命令类似:
acme.sh --issue --dns dns_ali -d “*.app.yourdomain.com” acme.sh --issue --dns dns_ali -d “*.api.yourdomain.com”6.3 证书续期与状态检查
自动化续期是acme.sh的最大亮点,但我们也需要知道如何手动检查和干预。
- 手动续期:
acme.sh --renew -d “*.yourdomain.com” --force。--force参数表示即使证书未到期也强制更新。通常在测试续期流程或紧急更换密钥时使用。 - 查看证书信息:
acme.sh --info -d “*.yourdomain.com”。这会显示证书的路径、到期时间、以及配置的reloadcmd等信息。 - 查看所有证书:
acme.sh --list。列出当前账户下所有由acme.sh管理的证书。
6.4 我踩过的那些“坑”与解决方案
坑:cron任务不执行或执行失败
- 现象:证书快过期了却没自动续期。
- 排查:首先检查cron日志
sudo grep CRON /var/log/syslog,看是否有acme.sh的任务记录。更直接的方法是查看acme.sh自己的日志:~/.acme.sh/acme.sh.log。 - 常见原因与解决:
- 环境变量丢失:cron执行的环境与用户Shell环境不同,如果你在
.bashrc中设置了Ali_Key和Ali_Secret,cron是读不到的。最可靠的方案是在使用acme.sh --issue命令时,直接通过--dns选项的扩展参数传递密钥,或者使用acme.sh的--config-home功能将配置存到特定位置。但更简单的方法是:在~/.acme.sh/account.conf文件中直接写入SAVED_Ali_Key=‘xxx‘和SAVED_Ali_Secret=‘xxx‘。acme.sh会自动读取这个文件中的变量。 - sudo权限问题:你的
--reloadcmd是sudo systemctl reload nginx。cron任务默认以当前用户运行,执行sudo时需要密码。解决方法是为该用户配置Nginx服务的免密码sudo。执行sudo visudo,在文件末尾添加一行:debian ALL=(ALL) NOPASSWD: /bin/systemctl reload nginx。这样debian用户就可以无需密码执行reload nginx了。
- 环境变量丢失:cron执行的环境与用户Shell环境不同,如果你在
坑:DNS验证超时
- 现象:执行
--issue命令时,卡在 “Pending, The CA is processing your order, please just wait.” 或 “Verify error:Invalid response from” 然后失败。 - 排查:这通常是DNS记录没有及时在全球生效(DNS传播延迟)。阿里云添加记录通常很快,但某些地区DNS缓存可能导致延迟。
- 解决:
- 增加等待时间:
acme.sh默认等待120秒。你可以通过--dnssleep参数增加等待时间,例如--dnssleep 300等待5分钟。 - 使用备用DNS:在命令后添加
--server letsencrypt可以切换到Let‘s Encrypt的主服务器,有时有奇效。或者使用--server buypass或--server zerossl等其他ACME CA(可能需要额外注册)。 - 手动验证:在命令执行后,立刻去阿里云DNS控制台查看,是否已经生成了
_acme-challenge的TXT记录。然后用dig TXT _acme-challenge.yourdomain.com @8.8.8.8命令查询谷歌DNS,看是否已解析。如果控制台有但公共DNS查不到,就耐心等一会儿。
- 增加等待时间:
- 现象:执行
坑:续期后Nginx未重载,服务仍使用旧证书
- 现象:查看
acme.sh日志发现续期成功,但浏览器访问网站提示证书过期。 - 排查:检查
~/.acme.sh/*.yourdomain.com/*.conf配置文件中的Le_ReloadCmd是否正确。检查/etc/nginx/ssl/yourdomain.com/下的证书文件修改时间是否已更新。 - 解决:很可能
reloadcmd命令执行失败了。可以手动运行一下配置中的命令看是否有报错。确保Nginx的配置文件语法始终正确(nginx -t),因为如果配置有误,systemctl reload nginx会静默失败。一个更健壮的做法是将reloadcmd写成一个脚本,在脚本里先检查语法,再重载,并记录日志:
然后给脚本执行权限,并在# 创建脚本 /usr/local/bin/reload-nginx.sh #!/bin/bash if sudo nginx -t > /dev/null 2>&1; then sudo systemctl reload nginx echo “[$(date)] Nginx reloaded for new cert.” >> /var/log/acme-renew.log else echo “[$(date)] Nginx config test failed! Cannot reload.” >> /var/log/acme-renew.log # 可以在这里添加发送报警邮件的逻辑 fi--install-cert时指定--reloadcmd “/usr/local/bin/reload-nginx.sh”。
- 现象:查看
7. 生产环境维护与监控建议
当你的网站跑起来后,证书管理就变成了一个后台静默工作。为了确保万无一失,我建议你建立简单的监控。
7.1 证书过期监控
虽然acme.sh会自动续期,但监控是最后一道防线。你可以用简单的Shell脚本定期检查证书过期时间,并发送通知。
#!/bin/bash # check_cert_expiry.sh DOMAIN=“*.yourdomain.com” CERT_FILE=“/etc/nginx/ssl/yourdomain.com/fullchain.pem” # 获取证书过期时间(Unix时间戳) EXPIRY_DATE=$(openssl x509 -enddate -noout -in “$CERT_FILE” | cut -d= -f2) EXPIRY_TIMESTAMP=$(date -d “$EXPIRY_DATE” +%s) # 获取当前时间戳 CURRENT_TIMESTAMP=$(date +%s) # 计算剩余天数 DAYS_LEFT=$(( (EXPIRY_TIMESTAMP - CURRENT_TIMESTAMP) / 86400 )) # 设置告警阈值(例如小于10天) WARNING_DAYS=10 if [ “$DAYS_LEFT” -lt “$WARNING_DAYS” ]; then # 发送告警,这里可以用邮件、Slack、钉钉机器人等 echo “警告: 证书 ${DOMAIN} 将在 ${DAYS_LEFT} 天后过期!” | \ mail -s “SSL证书过期警告” your-email@example.com # 或者调用curl发送到你的监控平台 # curl -X POST -H ‘Content-Type: application/json‘ -d “{\“text\“:\“证书 ${DOMAIN} 即将过期!\“}” YOUR_WEBHOOK_URL fi将这个脚本加入cron,每周运行一次即可。
7.2 密钥与证书文件的权限安全
/etc/nginx/ssl/目录下的私钥文件(.key或.pem)是最高机密。务必确保其权限设置正确:
sudo chown root:root /etc/nginx/ssl/yourdomain.com/ sudo chmod 700 /etc/nginx/ssl/yourdomain.com/ # 目录仅root可读写执行 sudo chown root:root /etc/nginx/ssl/yourdomain.com/privkey.pem sudo chmod 600 /etc/nginx/ssl/yourdomain.com/privkey.pem # 私钥仅root可读证书文件(.pem或.cer)可以放宽一点权限,因为里面是公开信息,但通常也设置为644(root可读写,其他人只读)。
7.3 定期回顾与升级
- 升级acme.sh:
acme.sh本身会定期自动升级。你也可以手动运行acme.sh --upgrade。新版本可能会修复bug或增加对新DNS API的支持。 - 回顾RAM权限:每年检查一次阿里云RAM用户的AccessKey,如果长期未使用,考虑轮换。检查RAM策略,确保权限依然是最小化的。
- 测试续期流程:在证书到期前几个月,可以手动运行一次
acme.sh --renew -d “*.yourdomain.com” --force来测试整个续期、安装、重载流程是否依然顺畅。