目录
- 一、为什么需要通配符证书
- 二、环境准备
- [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 以管理员身份运行(非常重要)
证书签发要写系统计划任务、访问证书存储,必须管理员。两种方式:
- 进到解压目录,右键
wacs.exe→ 以管理员身份运行 - 或者像本文后面那样,写一个自带「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,主账号可以):
- 进入「访问控制 RAM」控制台 → 左侧用户 → 创建用户
- 登录名称:
win-acme-dns(自定义) - 访问方式:只勾「通过 OpenAPI 调用访问」(不需要控制台登录,这用户只给脚本用)
- 登录名称:
- 创建成功后会给出 AccessKey ID 和 AccessKey Secret 。Secret 只显示这一次,关掉就再也看不到,立刻复制保存。
- 创建一条自定义权限策略(最小权限,只给 DNS 相关操作,且限定你的域名),把下面 JSON 贴到「脚本编辑」里:
json
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"alidns:AddDomainRecord",
"alidns:DeleteDomainRecord",
"alidns:DescribeDomainRecords"
],
"Resource": "acs:alidns:*:*:domain/yourdomain.com"
}
]
}
- 回到用户列表,给
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 |
九、总结
整套流程下来,你得到的是:
- 一张覆盖
*.yourdomain.com+yourdomain.com的免费 Let's Encrypt 通配符证书 - 部署在 Nginx 的 5050 端口 HTTPS 服务
- 一套启停脚本,双击就能管理 Nginx
- (进阶)阿里云 RAM + 自动脚本,证书到期全自动续期
- Java 侧部署:推荐 Nginx 终结 HTTPS 再反代到 Spring Boot 的 HTTP(证书只在一处管理);若 Java 直连 TLS,注意 JDK 版本坑与「重启才生效」
最值得记住的三句话:脚本必须纯 ASCII、--dnsscript 必须用绝对路径、正式签发前先用 --test 跑通。踩过这几个坑,这套流程就能稳定跑很多年。



