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

使用acme.sh为Nginx部署Let‘s Encrypt泛域名SSL证书实战指南
1. 项目概述为什么我们需要泛域名SSL证书在今天的互联网环境中HTTPS早已不是“加分项”而是“必选项”。无论是搜索引擎的排名权重还是浏览器对非HTTPS站点的安全警告都在倒逼每一个网站管理员必须为自己的服务启用SSL/TLS加密。对于个人开发者、中小团队或是拥有多个子域名的业务系统来说一个常见的痛点随之而来我们可能需要为blog.example.com、api.example.com、static.example.com等多个子域名分别申请和部署证书。手动为每一个子域名重复申请、部署和续期的过程不仅繁琐更容易出错。这时泛域名SSL证书Wildcard SSL Certificate的价值就凸显出来了。一张*.example.com的证书可以同时保护主域名example.com以及其下无限数量的任意子域名如a.example.com、b.c.example.com。这极大地简化了证书管理流程尤其适合微服务架构、多租户SaaS平台或内部测试环境。而Nginx作为市场占有率最高的Web服务器之一其灵活且强大的配置能力使得它成为部署和管理这类证书的理想平台。本文将从一个运维工程师的实战视角出发手把手带你完成从零开始为你的域名申请一张免费的泛域名SSL证书并将其无缝部署到Nginx服务器上实现全站HTTPS化。整个过程不仅会涵盖标准操作步骤更会深入那些官方文档很少提及的“坑”与“技巧”确保你能一次部署成功并长期稳定运行。2. 核心概念与方案选型免费 vs 付费ACME协议与自动化在动手之前我们必须理清几个核心概念这决定了后续整个流程的顺畅度。2.1 泛域名证书的类型与选择泛域名证书主要分为付费商业证书和免费证书两种。付费商业证书通常由DigiCert、Sectigo、GlobalSign等知名CA证书颁发机构签发。它们提供更高的保险额度、更严格的身份验证OV/EV证书、以及更完善的技术支持。如果你的业务涉及金融、电商等对信任度要求极高的场景商业证书是更稳妥的选择。免费泛域名证书的王者无疑是Lets Encrypt。它通过自动化证书管理环境ACME协议提供了完全自动化、免费的证书签发服务。其签发的证书是DV域名验证类型有效期仅为90天但正因为有效期短它强制你建立自动化续期流程从长期看反而提升了运维的健壮性。对于绝大多数博客、测试环境、内部系统和初创项目Lets Encrypt的免费泛域名证书是完全足够且最佳的选择。注意Lets Encrypt对泛域名证书的申请有严格限制。它只支持通过DNS-01挑战验证方式来申请泛域名证书。这意味着你必须能够通过API操作你域名的DNS解析记录添加特定的TXT记录而不能使用HTTP-01挑战在网站根目录放置文件的方式。这一点至关重要也是很多新手卡住的第一步。2.2 自动化工具选型Certbot 与 acme.sh要与Lets Encrypt的ACME协议交互我们需要一个客户端工具。主流选择有两个CertbotLets Encrypt官方推荐的工具由EFF电子前沿基金会维护。功能全面社区活跃插件生态丰富。对于Nginx它有专门的插件可以自动修改配置并重载服务实现“一键HTTPS”。但其自动化DNS插件依赖于你DNS服务商的官方API支持如果你的DNS提供商不在其支持列表内配置会稍显复杂。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 服务器与域名条件一台运行Linux的服务器本文以Ubuntu 20.04/22.04或CentOS 7/8为例。你需要拥有root或sudo权限。一个属于你的域名例如yourdomain.com。你需要拥有该域名的管理权限因为我们要操作它的DNS记录。域名DNS解析服务商并且该服务商提供可用的API接口用于自动添加TXT记录。例如Cloudflare、阿里云、腾讯云DNSPod、华为云等。请提前登录控制台获取API密钥或Token。Cloudflare需要Global API Key或更安全的API Token需具备Zone:DNS:Edit权限。阿里云需要AccessKey ID和AccessKey Secret建议使用子账户RAM权限。腾讯云DNSPod需要SecretId和SecretKey。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用户例如ubuntu或ec2-user身份执行以下命令curl https://get.acme.sh | sh -s emailyour-emailexample.com将your-emailexample.com替换为你自己的邮箱这个邮箱用于接收证书到期提醒和ACME协议相关通知。安装脚本会做以下几件事将acme.sh安装到~/.acme.sh/目录下。为你创建一个新的crontab作业用于每天自动检查并续期即将过期的证书。为当前Shell会话添加一个别名acme.sh指向安装的脚本。你需要重新登录或执行source ~/.bashrc或~/.zshrc来使别名生效。4.2 配置DNS API凭证关键步骤这是申请泛域名证书最核心的一步。你需要根据你的DNS服务商设置相应的环境变量。以下以Cloudflare和阿里云为例。方案A使用Cloudflare API Token推荐更安全在Cloudflare控制台进入“我的个人资料” - “API 令牌” - 创建令牌。选择“编辑区域 DNS”模板。在“区域资源”中选择“包括” - “特定区域” - 选择你的域名。创建后复制生成的API令牌。在服务器上设置环境变量export CF_Token你的API令牌 export CF_Account_ID你的账户ID # 在Cloudflare控制台首页右侧可以找到 export CF_Zone_ID你的区域ID # 在域名概述页面右侧可以找到方案B使用阿里云RAM子账户AccessKey登录阿里云控制台进入“访问控制RAM”。创建一个专门用于DNS API操作的用户为其附加“管理云解析(DNS)的权限”策略如AliyunDNSFullAccess。为该用户创建AccessKey保存好AccessKey ID和AccessKey Secret。在服务器上设置环境变量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会自动在你的DNS服务商处为_acme-challenge.yourdomain.com添加一条临时的TXT记录其值是一串特定的校验码。等待DNS记录在全球生效脚本会自动检测。向Lets Encrypt的服务器发起验证请求。验证通过后签发证书和私钥。自动清理刚才添加的临时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/*.cer6.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; # } }配置要点解析两个Server块第一个处理HTTP(80端口)仅做301重定向。第二个处理HTTPS(443端口)承载实际业务。server_name我们使用了*.yourdomain.com来匹配所有子域名这正是泛域名证书的威力所在。ssl_certificate必须指向完整证书链文件fullchain.cer如果只指向域名证书部分浏览器会因无法构建信任链而报错。ssl_certificate_key指向私钥文件。SSL优化参数ssl_protocols和ssl_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你可以任意编一个子域名应该能看到绿色的安全锁标志并且证书信息显示颁发者为 “Lets Encrypt”同时保护了主域名和所有子域名。7. 自动化续期与部署Lets 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这条命令做了三件事--key-file和--fullchain-file告诉acme.sh当证书续期后应该将新的私钥和完整证书链文件复制到我们指定的固定路径/etc/nginx/ssl/...。--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挂载到容器内。在宿主机上申请和更新证书按照前文步骤在宿主机上使用acme.sh申请证书并设置--reloadcmd为docker exec nginx_container nginx -s reload。挂载证书目录在运行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容器内Nginx配置容器内的Nginx配置文件中的ssl_certificate和ssl_certificate_key路径应指向挂载进来的路径例如/etc/nginx/ssl/yourdomain.com/fullchain.cer。这样当宿主机上的acme.sh续期证书并执行docker exec重载命令后容器内的Nginx就能读到最新的证书文件了。整个流程走下来从理解泛域名证书的价值到选择工具、申请证书再到Nginx部署和自动化续期我们完成了一个生产级HTTPS站点的完整搭建。关键在于理解DNS-01验证的原理并熟练运用acme.sh的DNS API和--reloadcmd钩子来实现自动化。这套组合拳不仅能用于泛域名也适用于单域名证书的自动化管理是现代运维中提升效率和可靠性的必备技能。