使用acme.sh为Nginx部署Let‘s Encrypt泛域名SSL证书实战指南

使用acme.sh为Nginx部署Let‘s Encrypt泛域名SSL证书实战指南

1. 项目概述:为什么我们需要泛域名SSL证书?

在今天的互联网环境中,HTTPS早已不是“加分项”,而是“必选项”。无论是搜索引擎的排名权重,还是浏览器对非HTTPS站点的安全警告,都在倒逼每一个网站管理员必须为自己的服务启用SSL/TLS加密。对于个人开发者、中小团队或是拥有多个子域名的业务系统来说,一个常见的痛点随之而来:我们可能需要为blog.example.comapi.example.comstatic.example.com等多个子域名分别申请和部署证书。手动为每一个子域名重复申请、部署和续期的过程,不仅繁琐,更容易出错。

这时,泛域名SSL证书(Wildcard SSL Certificate)的价值就凸显出来了。一张*.example.com的证书,可以同时保护主域名example.com以及其下无限数量的任意子域名(如a.example.comb.c.example.com)。这极大地简化了证书管理流程,尤其适合微服务架构、多租户SaaS平台或内部测试环境。而Nginx作为市场占有率最高的Web服务器之一,其灵活且强大的配置能力,使得它成为部署和管理这类证书的理想平台。

本文将从一个运维工程师的实战视角出发,手把手带你完成从零开始,为你的域名申请一张免费的泛域名SSL证书,并将其无缝部署到Nginx服务器上,实现全站HTTPS化。整个过程不仅会涵盖标准操作步骤,更会深入那些官方文档很少提及的“坑”与“技巧”,确保你能一次部署成功,并长期稳定运行。

2. 核心概念与方案选型:免费 vs 付费,ACME协议与自动化

在动手之前,我们必须理清几个核心概念,这决定了后续整个流程的顺畅度。

2.1 泛域名证书的类型与选择

泛域名证书主要分为付费商业证书和免费证书两种。

付费商业证书通常由DigiCert、Sectigo、GlobalSign等知名CA(证书颁发机构)签发。它们提供更高的保险额度、更严格的身份验证(OV/EV证书)、以及更完善的技术支持。如果你的业务涉及金融、电商等对信任度要求极高的场景,商业证书是更稳妥的选择。

免费泛域名证书的王者,无疑是Let's Encrypt。它通过自动化证书管理环境(ACME)协议,提供了完全自动化、免费的证书签发服务。其签发的证书是DV(域名验证)类型,有效期仅为90天,但正因为有效期短,它强制你建立自动化续期流程,从长期看反而提升了运维的健壮性。对于绝大多数博客、测试环境、内部系统和初创项目,Let's Encrypt的免费泛域名证书是完全足够且最佳的选择。

注意:Let's Encrypt对泛域名证书的申请有严格限制。它只支持通过DNS-01挑战验证方式来申请泛域名证书。这意味着你必须能够通过API操作你域名的DNS解析记录(添加特定的TXT记录),而不能使用HTTP-01挑战(在网站根目录放置文件)的方式。这一点至关重要,也是很多新手卡住的第一步。

2.2 自动化工具选型:Certbot 与 acme.sh

要与Let's Encrypt的ACME协议交互,我们需要一个客户端工具。主流选择有两个:

  1. Certbot:Let's Encrypt官方推荐的工具,由EFF(电子前沿基金会)维护。功能全面,社区活跃,插件生态丰富。对于Nginx,它有专门的插件可以自动修改配置并重载服务,实现“一键HTTPS”。但其自动化DNS插件依赖于你DNS服务商的官方API支持,如果你的DNS提供商不在其支持列表内,配置会稍显复杂。
  2. acme.sh:一个纯粹用Shell脚本编写的ACME客户端,以其轻量、强大和“无所不包”的DNS API支持而闻名。它几乎支持所有你能想到的DNS服务商(国内外主流如Cloudflare、阿里云、腾讯云DNSPod、GoDaddy等,甚至一些小众服务商),通过环境变量配置API密钥即可完成验证。它的设计哲学是“将ACME协议相关操作封装成简单的命令”,然后把证书文件交给你,由你自行部署到Nginx等服务器上,给予了运维人员更大的控制权。

我的选择与理由:在长期实践中,我倾向于使用acme.sh。原因有三:首先,其DNS API支持极其广泛,减少了因DNS服务商不受支持而带来的麻烦;其次,它作为Shell脚本,依赖极少,几乎可以在任何Linux/Unix环境下运行,包括资源受限的容器内;最后,它将证书签发和服务器配置解耦,让我能更清晰地掌控证书部署和Nginx配置的每一个环节,便于调试和编写自动化脚本。因此,下文将主要基于acme.sh进行演示。

3. 环境准备与前置条件

在开始申请证书之前,请确保你已经满足以下所有条件。

3.1 服务器与域名条件

  1. 一台运行Linux的服务器:本文以Ubuntu 20.04/22.04或CentOS 7/8为例。你需要拥有root或sudo权限。
  2. 一个属于你的域名:例如yourdomain.com。你需要拥有该域名的管理权限,因为我们要操作它的DNS记录。
  3. 域名DNS解析服务商:并且该服务商提供可用的API接口,用于自动添加TXT记录。例如Cloudflare、阿里云、腾讯云DNSPod、华为云等。请提前登录控制台,获取API密钥或Token。
    • Cloudflare:需要Global API Key或更安全的API Token(需具备Zone:DNS:Edit权限)。
    • 阿里云:需要AccessKey IDAccessKey Secret(建议使用子账户RAM权限)。
    • 腾讯云DNSPod:需要SecretIdSecretKey

3.2 服务器软件准备

确保服务器上已经安装了Nginx和用于后续操作的常用工具。

# 对于 Ubuntu/Debian sudo apt update sudo apt install -y nginx curl cron # 对于 CentOS/RHEL sudo yum install -y epel-release sudo yum install -y nginx curl cronie sudo systemctl enable --now nginx crond # 启动并设置开机自启 # 检查Nginx安装是否成功 nginx -v

安装完成后,可以先通过HTTP访问你的服务器IP,确认Nginx默认页面能正常显示。

4. 安装与配置 acme.sh 客户端

acme.sh的安装非常简单,它提倡“安装到用户家目录”的方式,避免污染系统目录。

4.1 一键安装

以非root用户(例如ubuntuec2-user)身份执行以下命令:

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

your-email@example.com替换为你自己的邮箱,这个邮箱用于接收证书到期提醒和ACME协议相关通知。

安装脚本会做以下几件事:

  1. acme.sh安装到~/.acme.sh/目录下。
  2. 为你创建一个新的crontab作业,用于每天自动检查并续期即将过期的证书。
  3. 为当前Shell会话添加一个别名acme.sh,指向安装的脚本。你需要重新登录或执行source ~/.bashrc(或~/.zshrc)来使别名生效。

4.2 配置DNS API凭证(关键步骤)

这是申请泛域名证书最核心的一步。你需要根据你的DNS服务商,设置相应的环境变量。以下以Cloudflare阿里云为例。

方案A:使用Cloudflare API Token(推荐,更安全)

  1. 在Cloudflare控制台,进入“我的个人资料” -> “API 令牌” -> 创建令牌。
  2. 选择“编辑区域 DNS”模板。
  3. 在“区域资源”中,选择“包括” -> “特定区域” -> 选择你的域名。
  4. 创建后,复制生成的API令牌。
  5. 在服务器上,设置环境变量:
export CF_Token="你的API令牌" export CF_Account_ID="你的账户ID" # 在Cloudflare控制台首页右侧可以找到 export CF_Zone_ID="你的区域ID" # 在域名概述页面右侧可以找到

方案B:使用阿里云RAM子账户AccessKey

  1. 登录阿里云控制台,进入“访问控制RAM”。
  2. 创建一个专门用于DNS API操作的用户,为其附加“管理云解析(DNS)的权限”策略(如AliyunDNSFullAccess)。
  3. 为该用户创建AccessKey,保存好AccessKey IDAccessKey Secret
  4. 在服务器上,设置环境变量:
export Ali_Key="你的AccessKey ID" export Ali_Secret="你的AccessKey Secret"

重要安全提示:这些环境变量包含敏感密钥。在生产环境中,绝对不要将它们硬编码在脚本或配置文件中。建议使用:

  • 临时申请:在申请证书的Shell会话中临时export,用完即结束会话。
  • 配置文件:将变量写入~/.acme.sh/account.conf文件(acme.sh会自动读取)。
  • 密钥管理服务:如AWS Secrets Manager、HashiCorp Vault等,在自动化脚本中动态获取。

acme.sh支持上百种DNS API,你可以通过acme.sh --issue --dns -d example.com命令查看列表,并根据提示设置对应的环境变量。

5. 申请泛域名SSL证书

环境变量配置好后,申请证书就是一行命令的事情。

acme.sh --issue --dns dns_cf -d "*.yourdomain.com" -d "yourdomain.com"

让我们拆解这个命令:

  • --issue:签发证书。
  • --dns dns_cf:指定使用DNS验证方式,并使用dns_cf模块(对应Cloudflare)。如果你用的是阿里云,这里应改为dns_ali
  • -d "*.yourdomain.com":指定要申请泛域名证书。
  • -d "yourdomain.com":同时申请根域名的证书。这是一个好习惯,确保yourdomain.com也能被同一张证书覆盖。

执行命令后,acme.sh会:

  1. 自动在你的DNS服务商处,为_acme-challenge.yourdomain.com添加一条临时的TXT记录,其值是一串特定的校验码。
  2. 等待DNS记录在全球生效(脚本会自动检测)。
  3. 向Let's Encrypt的服务器发起验证请求。
  4. 验证通过后,签发证书和私钥。
  5. 自动清理刚才添加的临时TXT记录。这是acme.sh的一大优点,无需手动清理。

整个过程通常在一两分钟内完成。成功后,你会看到类似以下的输出,并被告知证书和密钥的存放路径,通常位于~/.acme.sh/*.yourdomain.com/目录下。

[Wed Apr 10 10:00:00 UTC 2024] Your cert is in: /home/user/.acme.sh/*.yourdomain.com/*.yourdomain.com.cer [Wed Apr 10 10:00:00 UTC 2024] Your cert key is in: /home/user/.acme.sh/*.yourdomain.com/*.yourdomain.com.key [Wed Apr 10 10:00:00 UTC 2024] The intermediate CA cert is in: /home/user/.acme.sh/*.yourdomain.com/ca.cer [Wed Apr 10 10:00:00 UTC 2024] And the full chain cert is in: /home/user/.acme.sh/*.yourdomain.com/fullchain.cer

这里最关键的两个文件是:

  • *.yourdomain.com.key:你的私钥文件。必须严格保密,它是证明你服务器身份的唯一凭证。
  • fullchain.cer完整证书链文件。它包含了你的域名证书和中间CA证书,Nginx配置中需要用到它。

6. 在Nginx中部署证书与配置HTTPS

证书申请好了,现在需要告诉Nginx在哪里找到它们,并启用HTTPS。

6.1 规划证书存放路径

不建议直接使用~/.acme.sh/下的证书文件,因为该目录结构可能随acme.sh更新而变化。最佳实践是将证书文件复制到一个固定的、Nginx有权限读取的目录。

# 创建一个专用于存放SSL证书的目录 sudo mkdir -p /etc/nginx/ssl/yourdomain.com # 将证书和私钥复制过去(注意替换路径中的用户名和域名) sudo cp ~/.acme.sh/*.yourdomain.com/fullchain.cer /etc/nginx/ssl/yourdomain.com/ sudo cp ~/.acme.sh/*.yourdomain.com/*.yourdomain.com.key /etc/nginx/ssl/yourdomain.com/ # 修改文件权限,确保私钥只有root可读 sudo chmod 600 /etc/nginx/ssl/yourdomain.com/*.key sudo chmod 644 /etc/nginx/ssl/yourdomain.com/*.cer

6.2 配置Nginx服务器块(Server Block)

假设你原本有一个HTTP的Nginx配置,监听80端口,服务器名为yourdomain.com。现在我们需要修改它,使其同时支持HTTPS,并强制将所有HTTP流量重定向到HTTPS。

找到你的Nginx站点配置文件,通常位于/etc/nginx/sites-available/目录下。我们创建一个新的或修改现有配置。

# /etc/nginx/sites-available/yourdomain.com # 1. HTTP 服务器块,用于重定向到HTTPS server { listen 80; listen [::]:80; server_name yourdomain.com *.yourdomain.com; # 匹配主域名和所有子域名 # 告诉浏览器这是一个永久重定向(301) return 301 https://$host$request_uri; } # 2. HTTPS 服务器块 server { # 监听443端口,并启用SSL listen 443 ssl http2; listen [::]:443 ssl http2; server_name yourdomain.com *.yourdomain.com; # 指定证书和私钥的路径 ssl_certificate /etc/nginx/ssl/yourdomain.com/fullchain.cer; ssl_certificate_key /etc/nginx/ssl/yourdomain.com/*.yourdomain.com.key; # SSL 性能与安全优化配置(强烈建议) ssl_protocols TLSv1.2 TLSv1.3; # 禁用不安全的TLS 1.0/1.1 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 /var/www/yourdomain.com/html; index index.html index.htm; location / { try_files $uri $uri/ =404; } # 可以在这里为不同子域名配置不同的反向代理或根目录 # location ~ ^/api/ { # proxy_pass http://localhost:3000; # } }

配置要点解析

  1. 两个Server块:第一个处理HTTP(80端口),仅做301重定向。第二个处理HTTPS(443端口),承载实际业务。
  2. server_name:我们使用了*.yourdomain.com来匹配所有子域名,这正是泛域名证书的威力所在。
  3. ssl_certificate:必须指向完整证书链文件(fullchain.cer),如果只指向域名证书,部分浏览器会因无法构建信任链而报错。
  4. ssl_certificate_key:指向私钥文件。
  5. SSL优化参数ssl_protocolsssl_ciphers的设置是为了禁用老旧、不安全的协议和加密方式,提升安全性。你可以使用 Mozilla SSL Configuration Generator 在线工具生成最适合你Nginx版本的配置。

6.3 启用配置并测试

# 检查Nginx配置语法是否正确(非常重要!) sudo nginx -t # 如果显示 `syntax is ok` 和 `test is successful`,则重载Nginx使配置生效 sudo systemctl reload nginx

现在,打开浏览器访问https://yourdomain.com以及https://any.yourdomain.com(你可以任意编一个子域名),应该能看到绿色的安全锁标志,并且证书信息显示颁发者为 “Let's Encrypt”,同时保护了主域名和所有子域名。

7. 自动化续期与部署

Let's Encrypt证书只有90天有效期,手动续期是不可接受的。幸运的是,acme.sh和系统自带的cron服务已经为我们做好了自动化。

7.1 理解 acme.sh 的续期机制

安装acme.sh时,它已经自动添加了一个每日运行的定时任务(cron job)。这个任务会检查所有由它管理的证书,如果证书剩余有效期小于30天,就会自动尝试续期。

续期的过程与申请类似:通过DNS API添加TXT记录完成验证,获取新证书。但是,获取新证书后,它默认不会自动覆盖我们复制到/etc/nginx/ssl/目录下的旧证书文件。

7.2 配置安装后钩子(--reloadcmd)

为了解决上述问题,我们需要在证书签发或续期成功后,触发一个自定义的命令来更新Nginx使用的证书文件并重载服务。这可以通过--install-cert命令或直接在签发时使用--reloadcmd参数来实现。

更推荐的方式是,在首次签发证书后,使用--install-cert命令来设置安装钩子:

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

这条命令做了三件事:

  1. --key-file--fullchain-file:告诉acme.sh,当证书续期后,应该将新的私钥和完整证书链文件复制到我们指定的固定路径(/etc/nginx/ssl/...)。
  2. --reloadcmd:指定一个在证书文件更新后需要执行的命令。这里我们使用sudo systemctl reload nginx让Nginx重新加载配置,使其使用新的证书。

执行此命令后,acme.sh会将这个“安装信息”记录下来。以后每次自动续期成功,它都会自动执行文件复制和Nginx重载操作,实现真正的“无人值守”续期。

实操心得--reloadcmd中的命令需要足够的权限来复制文件和重载Nginx。由于acme.sh通常以普通用户运行,而/etc/nginx/ssl/目录和systemctl reload nginx命令需要root权限,这里使用了sudo。你需要确保当前用户可以通过sudo执行systemctl reload nginx且无需密码(通过visudo配置),或者探索其他更安全的权限管理方式,例如将证书目录权限设置为允许acme.sh用户所属组写入。

7.3 验证自动化流程

你可以手动模拟一次续期来测试整个流程是否畅通:

# 强制续期证书(即使未到期) acme.sh --renew -d "*.yourdomain.com" --force # 观察输出日志,看是否成功执行了 `--reloadcmd` 中的命令。 # 检查证书文件的时间戳是否更新 sudo ls -la /etc/nginx/ssl/yourdomain.com/

8. 高级配置与疑难排错

即使按照上述步骤操作,在实际部署中仍可能遇到一些问题。以下是几个常见场景及其解决方案。

8.1 混合使用泛域名与独立域名证书

有时,你可能对大部分子域名使用泛域名证书,但对某个特定子域名(如secure.yourdomain.com)希望使用一张独立的、更高级别的OV证书。这在Nginx中完全可以共存。

server { listen 443 ssl http2; server_name secure.yourdomain.com; # 指向独立域名的证书和私钥 ssl_certificate /etc/nginx/ssl/secure.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/secure.yourdomain.com/private.key; ... # 其他配置 } server { listen 443 ssl http2; server_name *.yourdomain.com; # 指向泛域名证书和私钥 ssl_certificate /etc/nginx/ssl/yourdomain.com/wildcard_fullchain.cer; ssl_certificate_key /etc/nginx/ssl/yourdomain.com/wildcard.key; ... # 其他配置 }

Nginx会根据server_name来匹配请求,并使用对应块中定义的证书。注意配置顺序,更具体的域名(如secure.yourdomain.com)应该放在泛域名(*.yourdomain.com)前面。

8.2 证书申请失败常见原因

问题现象可能原因解决方案
Create new order error. Le_OrderFinalize not foundACME服务器暂时性错误或网络问题。等待几分钟后重试。检查服务器网络连通性。
Verify error: Invalid response from https://acme-v02.api.letsencrypt.org/...DNS记录的TXT值未正确传播或未设置。使用dig TXT _acme-challenge.yourdomain.com @8.8.8.8命令在全球不同DNS服务器上查询,确认记录已生效。检查DNS API密钥是否有误。
Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html服务器无法连接到Let‘s Encrypt的API端点。检查服务器防火墙是否放行了对外HTTPS(443)端口的访问。某些云服务器安全组也需要配置。
申请频率过高被限制Let‘s Encrypt有速率限制(同一域名每周最多签发50张证书)。等待限制解除。调试时可以使用--staging参数使用测试环境,避免触发生产环境限制。

8.3 Nginx配置SSL后无法访问或报错

问题现象可能原因解决方案
浏览器提示“连接不安全”、“证书无效”1. 证书链不完整。
2. 证书域名与访问的域名不匹配。
3. 系统时间不正确。
1. 确保Nginx配置中ssl_certificate指向的是fullchain.cer
2. 检查server_name是否包含你访问的域名。
3. 使用date命令检查服务器时间,并使用ntpdate同步。
nginx -t测试失败配置文件语法错误。根据错误提示行号检查配置文件,常见错误有括号未闭合、分号缺失、路径错误等。
443端口无法连接1. 防火墙未开放443端口。
2. Nginx未监听443端口。
3. 云服务商安全组未配置。
1.sudo ufw allow 443/tcp(如果使用UFW)。
2. 检查Nginx配置中是否有listen 443 ssl;
3. 登录云控制台,检查安全组/防火墙规则。

8.4 使用 Docker 运行 Nginx 的证书部署

如果你的Nginx运行在Docker容器中,思路是将宿主机的证书目录通过数据卷(Volume)挂载到容器内。

  1. 在宿主机上申请和更新证书:按照前文步骤,在宿主机上使用acme.sh申请证书,并设置--reloadcmddocker exec nginx_container nginx -s reload
  2. 挂载证书目录:在运行Nginx容器时,将宿主机的/etc/nginx/ssl目录挂载到容器内的对应路径(如/etc/nginx/ssl)。
    docker run -d --name nginx \ -p 80:80 -p 443:443 \ -v /etc/nginx/ssl:/etc/nginx/ssl:ro \ # 只读挂载证书 -v /path/to/nginx.conf:/etc/nginx/nginx.conf:ro \ nginx:alpine
  3. 容器内Nginx配置:容器内的Nginx配置文件中的ssl_certificatessl_certificate_key路径应指向挂载进来的路径,例如/etc/nginx/ssl/yourdomain.com/fullchain.cer

这样,当宿主机上的acme.sh续期证书并执行docker exec重载命令后,容器内的Nginx就能读到最新的证书文件了。

整个流程走下来,从理解泛域名证书的价值,到选择工具、申请证书,再到Nginx部署和自动化续期,我们完成了一个生产级HTTPS站点的完整搭建。关键在于理解DNS-01验证的原理,并熟练运用acme.sh的DNS API和--reloadcmd钩子来实现自动化。这套组合拳不仅能用于泛域名,也适用于单域名证书的自动化管理,是现代运维中提升效率和可靠性的必备技能。