Windows 下用 win-acme 申请 Let's Encrypt 通配符证书并部署到 Nginx、Java(保姆级教程)

目录

  • 一、为什么需要通配符证书
  • 二、环境准备
    • [2.1 系统要求](#2.1 系统要求)
    • [2.2 下载 win-acme](#2.2 下载 win-acme)
    • [2.3 以管理员身份运行(非常重要)](#2.3 以管理员身份运行(非常重要))
  • [三、手动添加 DNS 记录方式申请(最简单、零依赖)](#三、手动添加 DNS 记录方式申请(最简单、零依赖))
    • [3.1 准备两个文件](#3.1 准备两个文件)
    • [3.2 执行签发](#3.2 执行签发)
    • [3.3 到域名解析控制台添加 TXT 记录](#3.3 到域名解析控制台添加 TXT 记录)
    • [3.4 回车继续,完成签发](#3.4 回车继续,完成签发)
  • [四、证书格式与 Java 转换(JKS 的坑)](#四、证书格式与 Java 转换(JKS 的坑))
  • [五、部署到 Nginx](#五、部署到 Nginx)
    • [5.1 复制证书](#5.1 复制证书)
    • [5.2 修改 nginx.conf](#5.2 修改 nginx.conf)
    • [5.3 校验配置并启动](#5.3 校验配置并启动)
    • [5.4 启停脚本(双击即用)](#5.4 启停脚本(双击即用))
  • [六、在 Java 应用中部署证书](#六、在 Java 应用中部署证书)
    • [6.1 推荐架构:Nginx 终结 HTTPS,Java 跑内网 HTTP](#6.1 推荐架构:Nginx 终结 HTTPS,Java 跑内网 HTTP)
    • [6.2 Spring Boot 直接对外 HTTPS(PFX)](#6.2 Spring Boot 直接对外 HTTPS(PFX))
    • [6.3 Spring Boot 用 JKS(兼容老 JDK 8)](#6.3 Spring Boot 用 JKS(兼容老 JDK 8))
    • [6.4 外置 Tomcat(war 部署)配置](#6.4 外置 Tomcat(war 部署)配置)
    • [6.5 证书更新后如何让 Java 生效](#6.5 证书更新后如何让 Java 生效)
  • [七、进阶:全自动续期(阿里云 DNS API)](#七、进阶:全自动续期(阿里云 DNS API))
    • [7.1 创建 RAM 用户和 AK(最小权限)](#7.1 创建 RAM 用户和 AK(最小权限))
    • [7.2 填 AK 并运行自动脚本](#7.2 填 AK 并运行自动脚本)
  • 八、常见坑与排错(血泪总结)
  • 九、总结

一、为什么需要通配符证书

普通 SSL 证书只能保护一个域名(比如 www.yourdomain.com)。如果你的服务有很多子域名------api.yourdomain.com、app.yourdomain.com、m.yourdomain.com......每个都单独申请会非常麻烦。

通配符证书 (*.yourdomain.com)一张证书就能覆盖该域名下的所有子域名,还能顺带包含裸域名 yourdomain.com。

本文用 Let's Encrypt (免费、可信、90 天有效期、支持自动续期)配合 win-acme(Windows 上最成熟的 ACME 客户端)来完成。最终效果:

  • 一张证书覆盖 *.yourdomain.com + yourdomain.com
  • 部署到 Nginx,提供 HTTPS 服务(本文用 5050 端口做演示)
  • 进阶:配合阿里云 DNS API,证书到期全自动续期,零人工干预

二、环境准备

2.1 系统要求

  • Windows 10 / 11 或 Windows Server
  • 需要 .NET 运行时(win-acme 的运行依赖,下载页会标注所需版本,按提示装好即可)
  • 管理员权限:签发证书、创建续期计划任务都需要

2.2 下载 win-acme

打开 win-acme 官网(win-acme.com)的下载页,选择 pluggable 版(x64)。

https://github.com/win-acme/win-acme/releases/

为什么选 pluggable 版? 它体积小、绿色便携。但要知道一个关键限制:pluggable 版没有内置任何国内 DNS 服务商的官方插件 (阿里云 / 腾讯云不存在 --plugin dns.aliyun 这种东西)。所以本文的 DNS 验证统一用「Script 脚本验证」方案,理论上兼容任意 DNS 服务商。

把压缩包解压到任意目录,例如 C:\win-acme\。解压后里面有个 wacs.exe,这就是主程序。

2.3 以管理员身份运行(非常重要)

证书签发要写系统计划任务、访问证书存储,必须管理员。两种方式:

  1. 进到解压目录,右键 wacs.exe → 以管理员身份运行
  2. 或者像本文后面那样,写一个自带「UAC 自提升」的 .cmd 启动器,双击就提权

三、手动添加 DNS 记录方式申请(最简单、零依赖)

如果你暂时没有 DNS 服务商的 API 权限,或者只是想先跑通一次验证流程,用手动方式最省事:程序告诉你往哪加一条 TXT 记录,你到域名解析控制台加上,回车,完事。

代价:这种方式证书到期时你得手动再跑一次(第六章会讲全自动方案)。

3.1 准备两个文件

在 C:\win-acme\ 下建两个文件。

① manual-dns-create.ps1(win-acme 调用它来提示你添加 TXT 记录):

powershell 复制代码
# manual-dns-create.ps1
# win-acme 会以: create|delete {Identifier} {RecordName} {Token} 调用本脚本
param()
$action     = $args[0]   # create / delete
$identifier = $args[1]   # 例如 yourdomain.com
$recordName = $args[2]   # 例如 _acme-challenge.yourdomain.com
$token      = $args[3]   # TXT 记录值(挑战 token)

if ($action -eq 'create') {
    Write-Host "=============================================="
    Write-Host " 请到你的域名解析控制台添加如下 TXT 记录:"
    Write-Host "   主机记录 / Name : $recordName"
    Write-Host "   记录类型 / Type : TXT"
    Write-Host "   记录值 / Value  : $token"
    Write-Host "=============================================="
    Write-Host "添加完成后,回到本窗口按回车继续..."
    Read-Host | Out-Null
    exit 0
}

# delete 阶段不卡人,直接退出(手动方案由你自己删记录)
exit 0

② run-gzpg-manual-dns.cmd(启动器,已含管理员自提升 + 绝对路径,避免踩坑):

bat 复制代码
@echo off
:: 不是管理员就自提升
net session >nul 2>&1
if %errorLevel% neq 0 (
    echo [cert] 请求管理员权限...
    powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
    exit /b
)
cd /d "%~dp0"

set "EMAIL=you@example.com"

wacs.exe ^
  --source manual ^
  --host "*.yourdomain.com,yourdomain.com" ^
  --validation script ^
  --validationmode dns-01 ^
  --dnsscript "%~dp0manual-dns-create.ps1" ^
  --emailaddress %EMAIL% ^
  --accepttos ^
  --store pemfiles --pemfilespath "certs" ^
  --store pfxfile --pfxfilepath "certs/yourdomain.com.pfx" --pfxpassword "你的PFX密码"

pause

几个必须理解的参数:

参数 作用
--host "*.yourdomain.com,yourdomain.com" 同时申请通配符和 裸域名。注意 *.x 不包含 x 本身,必须一起写
--validation script --validationmode dns-01 用脚本做 DNS-01 验证。漏掉 --validation script 会回退到 selfhosting 直接报错且不打印 TXT,是个经典坑
--dnsscript "%~dp0manual-dns-create.ps1" 验证脚本路径。%~dp0 表示脚本所在目录的绝对路径------千万别用相对路径,UAC 提权后工作目录会变,相对路径会找不到文件
--store pemfiles / --store pfxfile 同时导出 Nginx 用的 PEM 和 Java 用的 PFX
--test(可选) 走 Let's Encrypt 测试环境,不限频,建议第一次先加上跑通,再去掉跑正式

3.2 执行签发

双击 run-gzpg-manual-dns.cmd(会自动提权)。程序开始运行,会停在一个提示处,告诉你去加 TXT 记录:

复制代码
==============================================
 请到你的域名解析控制台添加如下 TXT 记录:
   主机记录 / Name : _acme-challenge.yourdomain.com
   记录类型 / Type : TXT
   记录值 / Value  : xxxxxxxx一段随机tokenxxxxxxxx
==============================================
添加完成后,回到本窗口按回车继续...

3.3 到域名解析控制台添加 TXT 记录

登录你的域名解析后台(阿里云 / 腾讯云 / DNSPod 等都可以),添加一条记录:

  • 主机记录 / RR :_acme-challenge
  • 记录类型 :TXT
  • 记录值:上一步命令行打印出来的那串 token
  • TTL:默认即可

⚠️ 重要提醒 :每次 DNS 验证都会生成新的 token ,旧的要删掉或改成新值,不能只加一次就永远有效 。如果验证失败报 Incorrect TXT record,十有八九是 token 没换。

3.4 回车继续,完成签发

回到命令行按回车,win-acme 会轮询 DNS 确认记录生效(通常几十秒到一两分钟),然后签发证书。

成功后,certs/ 目录会生成:

复制代码
certs/
├─ _.yourdomain.com-chain.pem   # 证书 + 中间链,给 Nginx 用
├─ _.yourdomain.com-key.pem     # 私钥
├─ yourdomain.com.pfx           # 给 Java 用(PKCS12)
└─ yourdomain.com.jks           # Java KeyStore(见下一章)

四、证书格式与 Java 转换(JKS 的坑)

  • Nginx 直接用 chain.pem + key.pem 即可,无需转换。
  • Java (Spring Boot / RuoYi 这类)常用 JKS 或 PKCS12。我们已经导出了 .pfx(PKCS12 格式),Spring Boot 其实可以直接用 PFX;如果你需要传统的 .jks,做一步转换。

坑预警 :老版本 JDK(如 8u131)自带的 keytool 读不动新版 PBES2 加密的 PFX,会报 parseAlgParameters failed。解决方法是用 JDK 11+ 的 keytool 来转:

bat 复制代码
:: 用 JDK11+ 的 keytool 把 PFX 转成 JKS
"C:\你的JDK17\bin\keytool.exe" ^
  -importkeystore ^
  -srckeystore certs/yourdomain.com.pfx -srcstoretype PKCS12 ^
  -destkeystore certs/yourdomain.com.jks -deststoretype JKS ^
  -srcalias [pfx里的别名] -destalias yourdomain ^
  -deststorepass 你的JKS密码

转出来的 JKS 老 JDK 也能正常加载,密码记好。

转换好的 JKS 以及导出的 PFX,怎么在 Java 项目里真正用起来(Spring Boot / 外置 Tomcat,以及推荐的 Nginx 反代架构),见第六章。


五、部署到 Nginx

假设你已经装好 Nginx(本文以 D:\nginx-1.30.6 为例)。

5.1 复制证书

把 Nginx 需要的俩文件复制到 nginx/ssl/:

复制代码
D:\nginx-1.30.6\ssl\_.yourdomain.com-chain.pem
D:\nginx-1.30.6\ssl\_.yourdomain.com-key.pem

私钥是敏感文件,注意这个目录的访问权限,别给无关账号读。

5.2 修改 nginx.conf

编辑 conf/nginx.conf,加(或改)一个 HTTPS 的 server 块。下面示例只保留 5050 的 HTTPS,去掉了 80 的 HTTP(按你需求调整):

nginx 复制代码
server {
    listen       5050 ssl;
    server_name  yourdomain.com *.yourdomain.com;

    ssl_certificate      ssl/_.yourdomain.com-chain.pem;
    ssl_certificate_key  ssl/_.yourdomain.com-key.pem;

    ssl_protocols        TLSv1.2 TLSv1.3;
    ssl_ciphers          HIGH:!aNULL:!MD5;
    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;

    location / {
        root   html;
        index  index.html index.htm;
    }
}

要点:

  • listen 5050 ssl:监听 5050 并启用 SSL。5050 不是特权端口,不需要管理员也能绑。
  • server_name 同时写裸域名和通配符,证书才匹配所有子域名。
  • ssl_certificate 用的是 chain.pem(含完整中间链),别只用服务器证书那张,否则部分客户端会报证书链不完整。

5.3 校验配置并启动

打开管理员命令行,进到 Nginx 目录:

bat 复制代码
cd /d D:\nginx-1.30.6

:: 校验配置(必须带 -p 指定前缀目录!)
nginx.exe -t -p D:/nginx-1.30.6 -c conf/nginx.conf

:: 启动
nginx.exe -p D:/nginx-1.30.6 -c conf/nginx.conf

⚠️ 坑 :nginx -t 一定要带 -p D:/nginx-1.30.6,否则它按当前工作目录去解析 logs/、pid 的相对路径,会误报失败。

看到 test is successful 就说明配置没问题。启动后用浏览器访问 https://yourdomain.com:5050,地址栏应显示安全锁。

5.4 启停脚本(双击即用)

每次都敲命令太麻烦,封装一个 nginx-ctl.bat,双击默认就是启动,还支持停止 / 重载 / 重启 / 校验:

bat 复制代码
@echo off
setlocal
set NGINX_HOME=D:\nginx-1.30.6
set CONF=%NGINX_HOME%\conf\nginx.conf
set NGINX=%NGINX_HOME%\nginx.exe

REM 无参数默认启动(双击即用)
if "%1"==""          goto :start
if /i "%1"=="start"   goto :start
if /i "%1"=="stop"    goto :stop
if /i "%1"=="reload"  goto :reload
if /i "%1"=="restart" goto :restart
if /i "%1"=="test"    goto :test
if /i "%1"=="status"  goto :status
goto :usage

:start
echo [nginx-ctl] Testing config...
"%NGINX%" -t -p "%NGINX_HOME%" -c "%CONF%"
if errorlevel 1 (
  echo [nginx-ctl] Config test FAILED, abort start.
  goto :done
)
echo [nginx-ctl] Starting nginx in background...
start "" "%NGINX%" -p "%NGINX_HOME%" -c "%CONF%"
ping -n 2 127.0.0.1 >nul
netstat -an | findstr ":5050" >nul && (
  echo [nginx-ctl] OK: nginx is listening on 5050
) || (
  echo [nginx-ctl] WARN: port 5050 not responding. Check logs/error.log
)
goto :done

:stop
echo [nginx-ctl] Stopping nginx...
"%NGINX%" -s stop -p "%NGINX_HOME%" -c "%CONF%"
if errorlevel 1 (
  echo [nginx-ctl] Graceful stop failed, force killing...
  taskkill /F /IM nginx.exe >nul 2>&1
)
goto :done

:reload
echo [nginx-ctl] Reloading config...
"%NGINX%" -s reload -p "%NGINX_HOME%" -c "%CONF%"
goto :done

:restart
echo [nginx-ctl] Restarting nginx...
taskkill /F /IM nginx.exe >nul 2>&1
ping -n 2 127.0.0.1 >nul
start "" "%NGINX%" -p "%NGINX_HOME%" -c "%CONF%"
ping -n 2 127.0.0.1 >nul
goto :done

:test
"%NGINX%" -t -p "%NGINX_HOME%" -c "%CONF%"
goto :done

:status
tasklist | findstr /i nginx.exe >nul && echo nginx is RUNNING || echo nginx is NOT running
goto :done

:usage
echo Usage: nginx-ctl.bat [start^|stop^|reload^|restart^|test^|status]
echo   (double-click with no arg = start)

:done
echo.
echo [nginx-ctl] Done. Press any key to close.
pause >nul

用法:

bat 复制代码
nginx-ctl.bat            :: 双击 / 无参数 = 启动
nginx-ctl.bat stop       :: 停止
nginx-ctl.bat reload     :: 改完配置热加载(发新版不用重启)
nginx-ctl.bat restart    :: 重启
nginx-ctl.bat test       :: 校验配置
nginx-ctl.bat status     :: 看是否在跑

六、在 Java 应用中部署证书

上一章把证书部署到了 Nginx。如果你的后端是 Java,证书还有几种用法,按推荐程度从优到次:

6.1 推荐架构:Nginx 终结 HTTPS,Java 跑内网 HTTP

最省心的做法:让 Nginx 对外提供 HTTPS(第五章已做),再把请求反代到后端 Java 的 HTTP 端口。Java 应用完全不碰证书。

nginx 复制代码
# 在 nginx.conf 的 server 块里加反向代理
location /api/ {
    proxy_pass http://127.0.0.1:8080/;   
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

好处:证书只在 Nginx 一处管理,续期时只替换 Nginx 的 pem 并执行 nginx -s reload,Java 什么都不用动。这也是大多数生产环境的标配。

6.2 Spring Boot 直接对外 HTTPS(PFX)

如果你没有 Nginx 前置、想让 Java 自己终结 TLS,Spring Boot 内嵌 Tomcat 原生支持 PKCS12(.pfx),配置最简单:

yaml 复制代码
# application.yml
server:
  port: 8443
  ssl:
    enabled: true
    key-store: file:/path/to/yourdomain.com.pfx
    key-store-password: 你的PFX密码
    key-store-type: PKCS12
    key-alias: yourdomain        # 导出时的别名,不确定用 keytool -list 查

⚠️ 关键坑(JDK 版本) :第四章说过,老 JDK(如 8u131)连 keytool 都读不动 win-acme 导出的新版 PBES2 加密 PFX,运行时读 PFX 同理会炸 (parseAlgParameters failed)。所以:

  • 要么把运行 Spring Boot 的 JDK 升到 11+,新版 JSSE 能读 PBES2 加密的 PFX;
  • 要么走 6.3 用 JKS(老 JDK 能读 JKS)。

RuoYi 注意:配置写在 application.yml(或对应 profile 的 yml);如果是 jar 部署 ,证书放 jar 外面、用 file: 绝对路径引用,方便更新时只换文件不重打包。

6.3 Spring Boot 用 JKS(兼容老 JDK 8)

用第四章 JDK 11+ 的 keytool 把 PFX 转成的 JKS,老 JDK 8 也能正常加载:

yaml 复制代码
server:
  port: 8443
  ssl:
    enabled: true
    key-store: file:/path/to/yourdomain.com.jks
    key-store-password: 你的JKS密码
    key-store-type: JKS
    key-alias: yourdomain

6.4 外置 Tomcat(war 部署)配置

如果是把 war 包丢到外置 Tomcat,改 conf/server.xml,加一个 HTTPS Connector:

xml 复制代码
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
           maxThreads="150" SSLEnabled="true">
    <SSLHostConfig>
        <Certificate certificateKeystoreFile="/path/to/yourdomain.com.jks"
                     type="JKS"
                     certificateKeystorePassword="你的JKS密码" />
    </SSLHostConfig>
</Connector>

同时把原来 8080 的 HTTP Connector 的 redirectPort 指到 8443,或按需关闭 HTTP。

6.5 证书更新后如何让 Java 生效

  • 走 6.1(Nginx 反代) :换 Nginx 的 pem + nginx -s reload,Java 无感,最省事。
  • Java 直连证书(6.2 / 6.3 / 6.4) :Java 启动时把证书读进内存,不会自动热加载 。换证书后必须:① 替换磁盘上的 .pfx / .jks;② 重启 Spring Boot / Tomcat 才能生效。
  • 所以直连方案务必配合第七章的自动续期,并在续期脚本里追加「替换 Java 证书 + 重启服务」的步骤,否则续期了 Java 侧还是旧证书。

七、进阶:全自动续期(阿里云 DNS API)

手动方案每次到期都要人肉加 TXT,烦。想证书 90 天到期自动续期,就让程序自己调阿里云 DNS API 去增删 TXT 记录。

7.1 创建 RAM 用户和 AK(最小权限)

用主账号登录阿里云(RAM 子账号默认没权限给自己建 AK,主账号可以):

  1. 进入「访问控制 RAM」控制台 → 左侧用户 → 创建用户
    • 登录名称:win-acme-dns(自定义)
    • 访问方式:只勾「通过 OpenAPI 调用访问」(不需要控制台登录,这用户只给脚本用)
  2. 创建成功后会给出 AccessKey ID 和 AccessKey Secret 。Secret 只显示这一次,关掉就再也看不到,立刻复制保存。
  3. 创建一条自定义权限策略(最小权限,只给 DNS 相关操作,且限定你的域名),把下面 JSON 贴到「脚本编辑」里:
json 复制代码
{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "alidns:AddDomainRecord",
        "alidns:DeleteDomainRecord",
        "alidns:DescribeDomainRecords"
      ],
      "Resource": "acs:alidns:*:*:domain/yourdomain.com"
    }
  ]
}
  1. 回到用户列表,给 win-acme-dns 添加权限,选刚才这条策略。

7.2 填 AK 并运行自动脚本

把 AK / SK 填进自动启动器的两行(脚本会把它设为环境变量,PS1 自动读取):

bat 复制代码
set "ALIYUN_ACCESS_KEY_ID=你的真实AK"
set "ALIYUN_ACCESS_KEY_SECRET=你的真实SK"

自动验证脚本 aliyun-dns-auto.ps1 用 HMAC-SHA1 签名调用阿里云 alidns API,自动完成「添加 TXT → 等 DNS 生效 → 删除 TXT」的全过程,无需任何人工输入,因此续期计划任务能无人值守运行。核心逻辑(节选):

powershell 复制代码
# aliyun-dns-auto.ps1(节选核心)
# win-acme 调用: create|delete {Identifier} {RecordName} {Token}
$ak = $env:ALIYUN_ACCESS_KEY_ID
$sk = $env:ALIYUN_ACCESS_KEY_SECRET

# ... HMAC-SHA1 签名、构造请求 ...

if ($action -eq 'create') {
    # 调 AddDomainRecord 添加 TXT,然后等 15 秒让阿里云同步
    Start-Sleep -Seconds 15
    exit 0
}
# delete: 用 DescribeDomainRecords 找到对应记录再 DeleteDomainRecord

完整脚本较长,思路就是「按阿里云 RPC 签名规范拼请求」。你可以照着 win-acme 官方文档的 Script 插件示例写,或直接复用本文配套的完整脚本。

双击运行自动启动器,先带 --test 跑 staging ,确认 TXT 记录能被自动增删、签发成功,再去掉 --test 跑正式签发。

跑通正式签发后,win-acme 会自动创建一个 Windows 计划任务,每约 60 天自动重新执行这套流程------从此证书续期你什么都不用管。


八、常见坑与排错(血泪总结)

现象 原因 解决
命令行满屏乱码 / 不是内部命令 .ps1/.cmd 含中文,在 GBK 的 cmd 下被误读破坏行结构 脚本纯 ASCII,中文注释能省就省
提权后找不到 dnsscript --dnsscript 用了相对路径,UAC 提权后工作目录变了 用 %~dp0 绝对路径
回退 selfhosting 报错、不打印 TXT 漏了 --validation script,只写了 --validationmode dns-01 补上 --validation script
nginx -t 报找不到 logs/pid 没带 -p 指定前缀目录 加 -p D:/nginx-1.30.6
Let's Encrypt 报限频 重复证书 5 张/周、总数 50 张/周 先用 --test 跑通再正式
keytool 转 PFX 报 parseAlgParameters failed 老 JDK(如 8u131)不兼容新 PBES2 加密 用 JDK 11+ 的 keytool 转 JKS
nginx -s stop 报 OpenEvent failed 之前的 master 进程变僵尸,stop event 丢了 直接 taskkill /F /IM nginx.exe 再启动
DNS 验证失败 Incorrect TXT record 每轮 token 都变,旧值没更新 删掉旧 TXT,填最新 token

九、总结

整套流程下来,你得到的是:

  1. 一张覆盖 *.yourdomain.com + yourdomain.com 的免费 Let's Encrypt 通配符证书
  2. 部署在 Nginx 的 5050 端口 HTTPS 服务
  3. 一套启停脚本,双击就能管理 Nginx
  4. (进阶)阿里云 RAM + 自动脚本,证书到期全自动续期
  5. Java 侧部署:推荐 Nginx 终结 HTTPS 再反代到 Spring Boot 的 HTTP(证书只在一处管理);若 Java 直连 TLS,注意 JDK 版本坑与「重启才生效」

最值得记住的三句话:脚本必须纯 ASCII、--dnsscript 必须用绝对路径、正式签发前先用 --test 跑通。踩过这几个坑,这套流程就能稳定跑很多年。