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

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



1. Why do you need a wildcard certificate?

Ordinary SSL certificates can only protect one domain name (such as www.yourdomain.com). If your service has many subdomains –api.yourdomain.com、app.yourdomain.com、m.yourdomain.com…It would be very troublesome to apply for each separately.

wildcard certificate(*.yourdomain.com) One certificate can cover all subdomains under the domain name, and can also include naked domain names. yourdomain.com。

For this article Let’s Encrypt(Free, trustworthy, 90-day validity, supports automatic renewal) Cooperation win-acme(the most mature ACME client on Windows) to complete. Final effect:

  • One certificate covers *.yourdomain.com + yourdomain.com
  • Deploy to Nginx to provide HTTPS service (this article uses port 5050 for demonstration)
  • Advanced: Cooperate with Alibaba Cloud DNS API, certificate expirationFully automatic renewalzero manual intervention

2. Environmental preparation

2.1 System requirements

  • Windows 10 / 11 或 Windows Server
  • need .NET runtime(The running dependency of win-acme, the required version will be marked on the download page, just follow the prompts to install it)
  • Administrator rights: Required for issuing certificates and creating renewal scheduled tasks

2.2 Download win-acme

Open the download page of win-acme official website (win-acme.com) and select pluggable version (x64).

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

Why choose the pluggable version? It is small, green and portable. But there is one key limitation to be aware of:The pluggable version does not have any built-in official plug-ins from domestic DNS service providers.(Alibaba Cloud/Tencent Cloud does not exist --plugin dns.aliyun something like this). Therefore, the DNS verification in this article uses the “Script script verification” solution, which is theoretically compatible with any DNS service provider.

Unzip the compressed package to any directory, for example C:\win-acme\. After unzipping, there is a wacs.exethis is the main program.

2.3 Run as administrator (very important)

Certificate issuance requires an administrator to write system scheduled tasks and access the certificate store. Two ways:

  1. Go to the unzipped directory and right-click wacs.exe → Run as administrator
  2. Or like later in this article, write one with “UAC self-improvement” .cmd Launcher, double-click to escalate privileges

3. Apply manually to add DNS records (the simplest, zero dependency)

If you do not have API permissions from the DNS service provider, or just want to go through the verification process first, it is easiest to use the manual method: the program tells you where to add a TXT record, you go to the domain name resolution console, add it, press Enter, and you are done.

cost: In this way, when the certificate expires, you have to run it again manually (Chapter 6 will talk about the fully automatic solution).

3.1 Prepare two files

exist C:\win-acme\ Download two files.

① manual-dns-create.ps1(win-acme calls this to prompt you to add a TXT record):

# manual-dns-create.ps1
# win-acme 会以: create|delete 
    Write-Host "=============================================="
    Write-Host " 请到你的域名解析控制台添加如下 TXT 记录:"
    Write-Host "   主机记录 / Name : $recordName"
    Write-Host "   记录类型 / Type : TXT"
    Write-Host "   记录值 / Value  : $token"
    Write-Host "=============================================="
    Write-Host "添加完成后,回到本窗口按回车继续..."
    Read-Host  
    Write-Host "=============================================="
    Write-Host " 请到你的域名解析控制台添加如下 TXT 记录:"
    Write-Host "   主机记录 / Name : $recordName"
    Write-Host "   记录类型 / Type : TXT"
    Write-Host "   记录值 / Value  : $token"
    Write-Host "=============================================="
    Write-Host "添加完成后,回到本窗口按回车继续..."
    Read-Host  
    Write-Host "=============================================="
    Write-Host " 请到你的域名解析控制台添加如下 TXT 记录:"
    Write-Host "   主机记录 / Name : $recordName"
    Write-Host "   记录类型 / Type : TXT"
    Write-Host "   记录值 / Value  : $token"
    Write-Host "=============================================="
    Write-Host "添加完成后,回到本窗口按回车继续..."
    Read-Host  调用本脚本
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 

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

② run-gzpg-manual-dns.cmd(The launcher already contains administrator self-improvement + absolute path to avoid pitfalls):

@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

severalMust understandParameters:

parameter effect
--host "*.yourdomain.com,yourdomain.com" Apply for wildcard at the same timeandNaked domain name. Notice *.x Not included x itself, must be written together
--validation script --validationmode dns-01 Use script to do DNS-01 verification.miss --validation script It will fall back to selfhosting and report an error directly without printing TXT.is a classic pit
--dnsscript "%~dp0manual-dns-create.ps1" Verify script path.%~dp0 Indicates the directory where the script is locatedabsolute path——Do not use relative paths. After UAC privileges are escalated, the working directory will change and files will not be found in relative paths.
--store pemfiles / --store pfxfile Export PEM for Nginx and PFX for Java at the same time
--test(optional) Take the Let’s Encrypt test environment,Unlimited frequencyit is recommended to add the run-through first, and then remove the run-through.

3.2 Execute issuance

double click run-gzpg-manual-dns.cmd(Priority will be elevated automatically). When the program starts running, it will stop at a prompt, telling you to add a TXT record:

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

3.3 Add TXT records to the domain name resolution console

Log in to your domain name resolution backend (Alibaba Cloud/Tencent Cloud/DNSPod, etc.) and add a record:

  • Host record/RR:_acme-challenge
  • record type:TXT
  • record value: The string of tokens printed by the command line in the previous step
  • TTL: Default is enough

⚠️ Important reminder: Generated every time DNS verificationnew tokenthe old one needs to be deleted or changed to a new value.You can’t just add it once and it will be effective forever.. If verification fails, report Incorrect TXT recordmost likely the token has not been changed.

3.4 Press Enter to continue and complete the issuance.

Return to the command line and press Enter. Win-acme will poll DNS to confirm that the record is valid (usually tens of seconds to one or two minutes), and then issue the certificate.

After success,certs/ The directory will generate:

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

4. Certificate format and Java conversion (JKS pitfalls)

  • Nginx Use directly chain.pem + key.pem That’s it, no conversion required.
  • Java(Spring Boot / RuoYi and the like) commonly used JKS or PKCS12. We have exported .pfx(PKCS12 format), Spring Boot can actually use PFX directly; if you need traditional .jksdo one step conversion.

Pit warning: Comes with old versions of JDK (such as 8u131) keytool Unable to read the new version of PBES2 encrypted PFX, it will report parseAlgParameters failed. The solution is to use JDK 11+ of keytool To transfer:

:: 用 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密码

The transferred JKS and old JDK can also be loaded normally, so remember the password.

How to use the converted JKS and exported PFX in Java projects (Spring Boot/external Tomcat, and the recommended Nginx anti-generation architecture), see Chapter 6.


5. Deploy to Nginx

Assume that you have installed Nginx (this article starts with D:\nginx-1.30.6 for example).

5.1 Copy certificate

Copy the two files required by Nginx to nginx/ssl/:

D:\nginx-1.30.6\ssl\_.yourdomain.com-chain.pem
D:\nginx-1.30.6\ssl\_.yourdomain.com-key.pem

The private key is a sensitive file. Pay attention to the access permissions of this directory and do not let irrelevant accounts read it.

5.2 Modify nginx.conf

edit conf/nginx.confadd (or change) an HTTPS server block. Example belowOnly keep 5050 HTTPS and remove 80 HTTP(Adjust to your needs):

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;
    }
}

Key points:

  • listen 5050 ssl: Listen to 5050 and enable SSL. 5050 is not a privileged port and can be bound without an administrator.
  • server_name Write naked domain name and wildcard at the same timethe certificate matches all subdomain names.
  • ssl_certificate used is chain.pem (including complete intermediate chain)don’t just use the server certificate, otherwise some clients will report that the certificate chain is incomplete.

5.3 Verify configuration and start

Open the administrator command line and go to the Nginx directory:

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

⚠️ pit:nginx -t Must bring -p D:/nginx-1.30.6otherwise it will parse according to the current working directory logs/、pid The relative path ofFalse positive failure。

See test is successful It means there is no problem with the configuration. After starting, access it with a browser https://yourdomain.com:5050the address bar should show a security lock.

5.4 Start and stop scripts (double-click to use)

It is too troublesome to type the command every time, so encapsulate one nginx-ctl.bat,Double-click to start by defaultalso supports stop/reload/restart/verify:

@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

usage:

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

6. Deploy certificates in Java applications

In the previous chapter, the certificate was deployed to Nginx. If your backend is Java, there are several ways to use the certificate, in descending order of recommendation:

6.1 Recommended architecture: Nginx terminates HTTPS, Java runs intranet HTTP

The most worry-free way: let Nginx provide HTTPS to the outside world (already done in Chapter 5), and then forward the request to the HTTP port of the back-end Java. Java applications don’t touch certificates at all.

# 在 nginx.conf 的 server 块里加反向代理
location /api/ {
    proxy_pass    
    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;
}

Advantages: Certificates are only managed in one place, Nginx.When renewing, only replace the pem of Nginx and execute nginx -s reloadJava does not need to touch anything. This is also standard in most production environments.

6.2 Spring Boot direct external HTTPS (PFX)

If you don’t have an Nginx front-end and want Java to terminate TLS by itself, Spring Boot’s built-in Tomcat natively supports PKCS12 (.pfx), the configuration is the simplest:

# 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 查

⚠️ Key pitfalls (JDK version): As mentioned in Chapter 4, the old JDK (such as 8u131) even keytool I can’t even read the new version of PBES2 encrypted PFX exported by win-acme.Reading PFX at runtime will cause an explosion in the same way.(parseAlgParameters failed). so:

  • Either putUpgrade the JDK running Spring Boot to 11+the new version of JSSE can read PBES2 encrypted PFX;
  • Or go to 6.3 and use JKS (old JDK can read JKS).

RuoYi Note: The configuration is written in application.yml(or the yml corresponding to the profile); if it is jar deploymentput the certificate outside the jar, use file: Absolute path reference makes it easier to change files without repackaging during updates.

6.3 Spring Boot uses JKS (compatible with old JDK 8)

Use Chapter 4 JDK 11+ keytool After converting PFX to JKS, the old JDK 8 can also be loaded normally:

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 External Tomcat (war deployment) configuration

If the war package is thrown to external Tomcat, change conf/server.xmladd an HTTPS Connector:


    
        
    

At the same time, change the original 8080 HTTP Connector redirectPort Point to 8443, or turn off HTTP as needed.

6.5 How to make Java effective after certificate renewal

  • Go to 6.1 (Nginx anti-generation): Replace Nginx pem + nginx -s reloadJava is insensitive and the most trouble-free.
  • Java Direct Connect Certificate (6.2 / 6.3 / 6.4): Java reads the certificate into memory when it starts.No automatic hot loading. After changing the certificate, you must: ① Replace the .pfx / .jks;② Restart Spring Boot / Tomcat can take effect.
  • Therefore, the direct connection solution must cooperate with the automatic renewal in Chapter 7, and add the steps of “replacing the Java certificate + restarting the service” in the renewal script. Otherwise, the Java side will still have the old certificate after renewal.

7. Advanced: Fully automatic renewal (Alibaba Cloud DNS API)

The manual plan requires human flesh to add TXT every time it expires, which is annoying. thinkCertificate automatically renews after 90 dayslet the program adjust the Alibaba Cloud DNS API to add and delete TXT records.

7.1 Create RAM user and AK (minimum privileges)

usemain accountLog in to Alibaba Cloud (RAM sub-accounts do not have permission to create AKs for themselves by default, but the main account does):

  1. Enter the “Access Control RAM” console → leftuser → Create user
    • Login name:win-acme-dns(customized)
    • Access method: Only check “Access via OpenAPI call”(No console login is required, this user is only used by scripts)
  2. Will be given after successful creation AccessKey ID and AccessKey Secret。Secret will only be displayed this time. If you turn it off, you will never see it again.copy and save immediately.
  3. Create aCustom permission policy(Minimum permissions, only for DNS related operations, and limited to your domain name), paste the following JSON into “Script Editor”:
{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "alidns:AddDomainRecord",
        "alidns:DeleteDomainRecord",
        "alidns:DescribeDomainRecords"
      ],
      "Resource": "acs:alidns:*:*:domain/yourdomain.com"
    }
  ]
}
  1. Return to the user list and give win-acme-dns Add permissionschoose this strategy just now.

7.2 Fill in AK and run automatic script

Fill in AK / SK into the two lines of the auto-starter (the script will set it as an environment variable and PS1 will read it automatically):

set "ALIYUN_ACCESS_KEY_ID=你的真实AK"
set "ALIYUN_ACCESS_KEY_SECRET=你的真实SK"

Automatic verification script aliyun-dns-auto.ps1 Call Alibaba Cloud with HMAC-SHA1 signature alidns API automatically completes the entire process of “adding TXT → waiting for DNS to take effect → deleting TXT” without any manual input, so the renewal schedule task can be run unattended. Core logic (excerpt):

# 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

The complete script is relatively long, and the idea is to “formulate requests according to Alibaba Cloud RPC signature specifications.” You can follow the Script plug-in example in the official win-acme documentation, or directly reuse the complete script provided in this article.

Double-click to run the auto-launcher,Take it first --test Run stagingconfirm that the TXT record can be automatically added, deleted, and signed successfully, and then delete it. --test Run officially issued.

After the Paotong is officially issued,win-acme will automatically create a Windows scheduled taskthis process is automatically re-executed every approximately 60 days – you don’t have to worry about anything from now on when renewing your certificate.


8. Common pitfalls and troubleshooting (summary of blood and tears)

Phenomenon reason solve
The command line is full of garbled characters/not an internal command .ps1/.cmd Contains Chinese, which was misread under GBK’s cmd and damaged the line structure. ScriptPure ASCIIChinese annotations can save you money
dnsscript not found after privilege escalation --dnsscript Relative paths are used, and the working directory changes after UAC privilege escalation. use %~dp0 absolute path
Falling back to selfhosting, reporting errors and not printing TXT Missed --validation scriptonly wrote --validationmode dns-01 make up --validation script
nginx -t Report that logs/pid cannot be found Didn’t bring it -p Specify prefix directory add -p D:/nginx-1.30.6
Let’s Encrypt reports limited frequency 5 repeat certificates/week, total 50 certificates/week Use first --test Running through becomes official again
keytool to PFX report parseAlgParameters failed Old JDK (such as 8u131) is not compatible with new PBES2 encryption Use JDK 11+ keytool to convert to JKS
nginx -s stop newspaper OpenEvent failed The previous master process became a zombie and the stop event was lost. direct taskkill /F /IM nginx.exe restart
DNS verification failed Incorrect TXT record The token changes every round, and the old value is not updated. Delete the old TXT and fill in the latest token

9. Summary

After the whole process, what you get is:

  1. one cover *.yourdomain.com + yourdomain.com ofFree Let’s Encrypt Wildcard Certificate
  2. Deployed on Nginx 5050 port HTTPS service
  3. setStart and stop scriptdouble-click to manage Nginx
  4. (Advanced) Alibaba Cloud RAM + automatic script, certificateFully automatic renewal upon expiration
  5. Java-side deployment: It is recommended that Nginx terminates HTTPS and then reverses it to Spring Boot’s HTTP (certificates are only managed in one place); if Java directly connects to TLS, pay attention to the JDK version pitfalls and “restart to take effect”

The three most memorable sentences:The script must be pure ASCII,--dnsscript You must use the absolute path and use it before officially issuing it. --test Run through. After stepping through these pitfalls, this process will run stably for many years.


image
image
image
image



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *