三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

acme.sh + 阿里云DNS实现泛域名SSL证书全自动申请与续期

acme.sh + 阿里云DNS实现泛域名SSL证书全自动申请与续期

1. 项目概述与核心价值

最近在折腾自己的几个小项目,发现SSL证书管理是个不大不小的麻烦事。尤其是那些带子域名的站点,每次申请、部署、续期都得手动操作一遍,费时费力不说,还容易忘。直到我发现了acme.sh这个神器,配合阿里云的DNS API,终于实现了全自动化的泛域名SSL证书申请与续期。这套方案在Debian服务器上跑,用Nginx做Web服务器,可以说是个人开发者和小团队的福音。

简单来说,这个方案能帮你做什么?它能让你用一行命令,为像*.yourdomain.com这样的泛域名,自动从Let‘s Encrypt申请到免费的SSL证书,并且自动完成Nginx的配置更新。最关键的是,证书90天到期前,它会自动帮你续期,整个过程完全无人值守,你再也不用惦记着证书过期这档子事了。无论你是运维新手,还是想优化现有流程的老手,这套基于acme.sh的方案都值得一试。它把复杂的证书生命周期管理,简化成了几个清晰的步骤,稳定性经过了我多个生产环境的长期考验。

2. 方案选型与工具解析

为什么选择acme.sh+ 阿里云DNS + Nginx这个组合?这里面的每一个选择都有其背后的考量。市面上实现ACME协议(自动化证书管理环境)的工具不少,比如Certbot、lego等。acme.sh的优势在于它纯粹用Shell脚本写成,依赖极少,几乎可以在任何Unix-like系统上运行,特别适合嵌入到各种自动化流程或资源受限的环境中。它的设计哲学是“零依赖”,安装后就是一个独立的脚本,管理起来非常清爽。

而选择阿里云的DNS API进行域名验证,是泛域名证书申请的关键。Let‘s Encrypt验证域名所有权主要有几种方式:HTTP-01(在网站根目录放文件)、DNS-01(在域名DNS解析里添加TXT记录)。对于泛域名证书(*.domain.com),HTTP-01验证方式是无法使用的,因为你不可能在每一个可能的子域名下都放置验证文件。因此,DNS-01是唯一的选择。这就需要你的域名DNS服务商提供API,允许通过脚本自动添加和删除TXT记录。阿里云DNS的API功能完善、文档清晰,且对于持有阿里云域名的用户来说集成最方便,成为了我的首选。

至于服务器系统选Debian,Web服务器选Nginx,这更多是基于个人技术栈和生态的考虑。Debian/Ubuntu系列在服务器领域占有率高,软件包管理成熟稳定;Nginx则以高性能和灵活的配置著称。acme.sh对这两者的支持都非常好,有现成的脚本可以自动将新证书安装到Nginx的配置目录并重载服务。整个技术栈搭配起来,形成了一个闭环的自动化解决方案。

注意:使用DNS-01验证方式,意味着acme.sh需要拥有你域名DNS的修改权限(通过API密钥)。请务必妥善保管API密钥,并遵循最小权限原则,只授予添加/删除TXT记录的权限。

3. 环境准备与依赖安装

在开始自动化之旅前,我们需要先把舞台搭好。这套方案的核心是acme.sh脚本,它需要运行在一个可以访问互联网,并且安装了基础工具的Linux环境里。以下步骤以Debian 11/12为例,其他发行版可能略有不同。

3.1 系统基础环境检查

首先,确保你的服务器系统是最新的,并且安装了curl和cron(用于定时任务)。通过SSH登录你的Debian服务器,执行以下命令:

sudo apt update && sudo apt upgrade -y sudo apt install curl cron -y

这两条命令会更新软件包列表,升级现有软件,并安装我们必需的curl(用于下载脚本)和cron(acme.sh依赖它来设置自动续期任务)。通常这些在干净的Debian系统中都是预装的,但检查一下总没错。

3.2 获取并安装acme.sh

acme.sh的安装极其简单。它推荐的方式是直接通过curl下载脚本并安装到用户目录下,避免污染系统目录。

curl https://get.acme.sh | sh -s email=my@example.com

my@example.com替换为你自己的邮箱地址。这个邮箱用于接收证书相关的通知(比如续期失败告警),非常重要。执行这条命令后,脚本会自动完成以下工作:

  1. acme.sh安装到~/.acme.sh/目录下。
  2. 为你创建一个bash的alias,方便使用:alias acme.sh=~/.acme.sh/acme.sh
  3. 自动为你创建一个cronjob(定时任务),用于每天检查证书是否需要续期。

安装完成后,你需要重新加载一下shell配置,或者新开一个终端窗口,让alias生效。也可以直接使用绝对路径~/.acme.sh/acme.sh来执行命令。

实操心得:安装过程可能会因为网络问题失败。如果curl下载缓慢,可以尝试多执行几次,或者先手动下载脚本再执行。安装成功后,务必检查~/.acme.sh/目录是否存在,并且acme.sh脚本有可执行权限。

3.3 配置阿里云DNS API密钥

这是整个流程中最需要谨慎的一步。我们需要在阿里云控制台创建一个具有DNS管理权限的RAM子用户,并使用其AccessKey。

  1. 登录阿里云控制台:访问阿里云官网并登录。
  2. 进入RAM访问控制:在控制台首页搜索“RAM”或直接访问访问控制(RAM)服务。
  3. 创建用户:在“用户”页面,点击“创建用户”。填写用户名,例如acme-dns,并务必勾选“控制台密码登录”和“编程访问”(后者用于生成API密钥)。点击确定。
  4. 授权策略:为用户授权。在用户列表找到刚创建的用户,点击“添加权限”。在系统策略中搜索并选择AliyunDNSFullAccess(管理云解析(DNS)的权限)。为了安全,强烈建议只授予这个权限,遵循最小权限原则。
  5. 获取AccessKey:创建成功后,在用户详情页的“认证管理”选项卡下,你会看到“创建AccessKey”的选项。点击创建,并务必立即保存弹出的AccessKey ID和AccessKey Secret。这个Secret只显示一次,关闭后就无法再次查看。

拿到Key之后,我们需要将其作为环境变量导出,供acme.sh使用。回到服务器终端,执行:

export Ali_Key="你的AccessKey ID" export Ali_Secret="你的AccessKey Secret"

重要警告:上述命令将密钥暴露在命令行历史和环境变量中,有一定风险。更安全的做法是:将这两个变量写入你的shell配置文件(如~/.bashrc~/.profile)中,然后source一下。或者,acme.sh也支持从文件中读取,你可以将密钥保存到~/.acme.sh/account.conf中,格式为SAVED_Ali_Key='xxx'SAVED_Ali_Secret='xxx'。选择一种你熟悉且安全的方式。

4. 申请泛域名SSL证书实操

环境配置妥当,密钥也已就位,现在可以开始申请我们的第一张泛域名证书了。这个过程是自动化的核心体现。

4.1 执行证书签发命令

假设你的域名是example.com,你想要一张覆盖*.example.com的证书。使用以下命令:

~/.acme.sh/acme.sh --issue --dns dns_ali -d '*.example.com' -d 'example.com'

让我们拆解一下这个命令:

  • --issue:表示执行签发证书的操作。
  • --dns dns_ali:指定使用DNS-01验证方式,并且使用阿里云(dns_ali)的DNS API。acme.sh支持几十种DNS服务商,dns_ali是其中之一。
  • -d '*.example.com':指定主域名,泛域名写法。这是证书的主体。
  • -d 'example.com':同时申请根域名的证书。这是一个好习惯,因为有些场景下浏览器可能直接访问根域名。Let‘s Encrypt允许一张证书包含多个域名(SAN证书)。

执行命令后,acme.sh会开始它的工作流:

  1. 向Let‘s Encrypt的ACME服务器发起证书申请。
  2. Let‘s Encrypt返回一个挑战(challenge),要求你在_acme-challenge.example.com这个子域名下设置一个特定的TXT记录值。
  3. acme.sh自动调用阿里云DNS API,为你添加这条TXT记录。
  4. 等待DNS记录全球同步(脚本会多次查询确认)。
  5. 验证通过后,acme.sh再次调用API,清理掉刚才添加的TXT记录。
  6. 最后,从Let‘s Encrypt服务器获取签发的证书和密钥。

整个过程完全自动化,你只需要等待1-2分钟即可。如果一切顺利,你会在最后看到“Cert success.”之类的成功信息。证书和密钥文件会被保存在~/.acme.sh/*.example.com/目录下。

4.2 证书文件解析与验证

申请成功后,进入证书目录查看一下:

ls -la ~/.acme.sh/*.example.com/

你会看到几个关键文件:

  • *.example.com.cerfullchain.cer:这是证书链文件,包含你的域名证书和中间CA证书。Nginx配置中通常使用这个。
  • *.example.com.key:这是你的私钥文件,必须严格保密。
  • ca.cer:中间CA证书。
  • *.example.com.confacme.sh为该域名保存的配置信息。

你可以使用openssl命令来验证证书的详细信息,比如查看有效期和包含的域名:

openssl x509 -in ~/.acme.sh/*.example.com/fullchain.cer -noout -text | grep -A1 -B1 "Subject:\|Not After\|DNS:"

这个命令会输出证书的主题(域名)、过期时间以及所有支持的域名(SAN列表),确认泛域名*.example.comexample.com都在其中。

注意事项:首次申请可能会因为DNS传播延迟而失败。如果遇到“Invalid domain”或验证超时错误,别着急。可以尝试在命令后加上--debug参数查看详细日志,或者手动去阿里云控制台检查TXT记录是否已添加成功。有时需要等待更长一点时间(如5-10分钟)让DNS生效,特别是某些地区的DNS缓存较慢。

5. 自动化安装与Nginx配置

证书申请下来只是第一步,让Nginx用上它并实现自动更新,才是“免维护”的关键。acme.sh提供了一个非常强大的--install-cert命令。

5.1 配置证书自动安装

我们不建议直接使用~/.acme.sh/目录下的证书文件,因为续期时这些文件会被更新。更好的做法是,让acme.sh在每次证书更新后,自动将新证书复制到标准位置(如/etc/ssl/),并重载Nginx。

首先,为你的域名创建一个存放证书的目录(通常遵循系统惯例):

sudo mkdir -p /etc/ssl/example.com/

然后,执行安装命令。这是一个需要仔细配置的一步:

~/.acme.sh/acme.sh --install-cert -d '*.example.com' \ --key-file /etc/ssl/example.com/private.key \ --fullchain-file /etc/ssl/example.com/fullchain.cer \ --reloadcmd "sudo systemctl reload nginx"

命令参数详解:

  • --install-cert:告诉acme.sh要安装证书。
  • -d '*.example.com':指定为哪个域名安装证书。
  • --key-file:指定私钥文件拷贝到的目标路径。
  • --fullchain-file:指定完整证书链文件拷贝到的目标路径。
  • --reloadcmd:这是核心!指定一个在证书安装/更新后需要执行的命令。这里我们设置为重载Nginx配置(sudo systemctl reload nginx)。reload是平滑重载,不会中断现有连接。

执行这个命令后,acme.sh会立即将当前的证书和密钥拷贝到指定位置。更重要的是,它会记住这个“安装配置”。以后每次该证书自动续期成功后,acme.sh都会自动执行这三步:1. 拷贝新证书和密钥到指定位置;2. 执行--reloadcmd定义的命令。这样就实现了真正的全自动续期。

5.2 配置Nginx以使用新证书

现在证书已经就位,我们需要修改Nginx的站点配置文件来使用它们。假设你有一个Nginx配置文件/etc/nginx/sites-available/your-site

找到配置中监听443端口(HTTPS)的server块,修改ssl_certificatessl_certificate_key指令,指向我们刚才安装的证书路径:

server { listen 443 ssl http2; server_name app.example.com www.example.com; # 指向acme.sh自动安装的证书路径 ssl_certificate /etc/ssl/example.com/fullchain.cer; ssl_certificate_key /etc/ssl/example.com/private.key; # 其他SSL优化配置(可选但推荐) ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # ... 其他配置(如root, index, location等) }

修改完成后,检查Nginx配置语法是否正确:

sudo nginx -t

如果显示“syntax is ok”和“test is successful”,就可以安全地重载Nginx了:

sudo systemctl reload nginx

现在,用浏览器访问你的HTTPS站点,应该能看到绿色的安全锁,证书信息显示是由“Let‘s Encrypt”签发,并且适用于你的泛域名。

实操心得:在--reloadcmd中使用sudo需要配置免密码。可以通过visudo命令编辑sudoers文件,为运行acme.sh的用户(通常是当前用户)添加一条规则,允许其无需密码执行systemctl reload nginx。例如:yourusername ALL=(ALL) NOPASSWD: /bin/systemctl reload nginx。这是实现自动化至关重要的一步,否则续期后无法自动重载Nginx。

6. 自动续期机制与状态监控

“一次配置,永久有效”的秘密就在于acme.sh内置的自动续期机制。理解并验证这个机制,你才能高枕无忧。

6.1 续期原理与计划任务

acme.sh在安装时,就自动为你创建了一个cronjob(定时任务)。你可以通过以下命令查看:

crontab -l

你应该能看到一行类似这样的记录(具体路径可能略有不同):

0 0 * * * "/home/username/.acme.sh"/acme.sh --cron --home "/home/username/.acme.sh" > /dev/null

这行配置的意思是:每天凌晨0点0分,执行一次acme.sh --cron命令。--cron参数会让acme.sh检查所有由它管理的证书,如果发现有证书距离过期时间小于30天(这是Let‘s Encrypt允许续期的最早时间),它就会自动触发续期流程。

续期流程和首次申请几乎一样:使用DNS-01验证,获取新证书,然后根据你之前用--install-cert设置的参数,将新证书安装到指定位置,并执行重载命令。整个过程在后台静默完成。

6.2 手动测试与状态检查

在依赖自动续期之前,强烈建议手动测试一下续期流程是否畅通。你可以使用--force参数来强制更新一张证书,即使它还没到期。

~/.acme.sh/acme.sh --renew -d '*.example.com' --force

观察命令输出,看它是否成功完成了“验证 -> 获取新证书 -> 安装证书 -> 执行reloadcmd”的全流程。同时,检查/etc/ssl/example.com/目录下证书文件的修改时间是否更新了,并用浏览器访问网站确认新证书已生效。

为了监控证书状态,你可以定期运行以下命令,查看所有由acme.sh管理的证书的过期时间:

~/.acme.sh/acme.sh --list

这个命令会以表格形式列出域名、证书存放路径、密钥路径以及最重要的到期日。你可以把它加入到自己的监控脚本中,或者设置一个每月检查一次的日历提醒,双重保险。

6.3 续期失败排查与日志分析

自动续期虽好,但并非万无一失。网络问题、API密钥失效、DNS服务商故障、磁盘空间不足等都可能导致续期失败。acme.sh会将每次cronjob的执行日志输出到标准错误,但我们在crontab里将其重定向到了/dev/null。为了调试,你可以修改crontab,将日志输出到一个文件:

0 0 * * * "/home/username/.acme.sh"/acme.sh --cron --home "/home/username/.acme.sh" >> /home/username/acme_cron.log 2>&1

这样,每天的续期尝试日志都会追加到acme_cron.log文件中。如果续期失败,你可以查看这个日志文件来定位问题。

常见的失败原因及解决思路:

  1. 阿里云API密钥失效:RAM用户的AccessKey可能被禁用或删除。去阿里云控制台检查并更新密钥,然后更新~/.acme.sh/account.conf文件或环境变量。
  2. DNS解析超时:在验证阶段,脚本查询TXT记录超时。可以尝试在签发命令中加入--dnssleep参数,增加等待DNS传播的时间,例如--dnssleep 120(等待120秒)。
  3. 证书安装失败--reloadcmd命令执行出错。检查命令路径是否正确,sudo权限是否已配置。可以手动执行一遍sudo systemctl reload nginx测试。
  4. Let‘s Encrypt速率限制:对于同一个域名或一组域名,Let‘s Encrypt有每周的签发次数限制。失败日志中通常会明确提示。遇到这种情况,只能等待限制解除。切勿在短时间内频繁重试,这只会加剧限制。

个人经验:我习惯在每次服务器重大变更(如系统升级、迁移)后,手动运行一次--renew --force来测试整个证书链路是否正常。同时,建议在服务器监控(如Prometheus + Grafana,或简单的健康检查脚本)中加入对证书过期时间的检查,比如提前15天告警,这样即使自动续期失败,你也有充足的人工干预时间。

← 返回列表