配置 Let's Encrypt 通配符(Windows + Apache + win-acme + Cloudflare)
最近把 Windows NAS 上的一些 Web 服务重新整理了一遍。
之前很多服务直接通过 HTTP 或公网端口访问,相当于一直“裸奔”。这次决定统一使用:
1 | Cloudflare DNS |
这样不仅可以给现有服务启用 HTTPS,以后增加新的子域名也不需要每次重新申请一张证书。
本文记录 Windows 环境下使用 Apache、win-acme 和 Cloudflare DNS 自动申请及续期 Let’s Encrypt 通配符证书的完整流程。
一、环境
示例环境:
1 | 系统: |
本文统一使用以下示例域名:
1 | example.com |
例如可能存在:
1 | nas.example.com |
相比给每个域名单独申请证书,更适合直接申请:
1 | *.example.com |
如果根域名本身也需要 HTTPS,则证书里同时加入:
1 | example.com |
注意:
1 | *.example.com |
可以覆盖:
1 | nas.example.com |
但不能覆盖:
1 | example.com |
也不能覆盖:
1 | a.b.example.com |
二、为什么使用 DNS-01
Let’s Encrypt 常见验证方式包括:
1 | HTTP-01 |
普通单域名可以使用 HTTP-01,但通配符证书:
1 | *.example.com |
必须使用 DNS-01。
DNS-01 的原理是临时创建类似下面的 TXT 记录:
1 | _acme-challenge.example.com |
Let’s Encrypt 查询到正确内容后,就确认申请者拥有这个域名。
如果域名 DNS 已经托管在 Cloudflare,就可以让 win-acme 使用 Cloudflare API 自动完成:
1 | 创建 TXT |
这样续期也可以完全自动完成。
三、Cloudflare 创建 API Token
不要直接使用 Cloudflare Global API Key。
推荐单独创建权限尽可能小的 API Token。
进入 Cloudflare Dashboard:https://dash.cloudflare.com/profile/api-tokens
1 | My Profile |
创建自定义 Token。
DNS 权限设置为:
1 | Zone |
另外建议加入:
1 | Zone |
资源范围只允许操作需要申请证书的 Zone,例如:
1 | example.com |
最终权限类似:
1 | Permissions: |
创建完成后 Cloudflare 只会显示一次 Token。
一定保存好。
不要把 API Token 公开
四、安装 win-acme Cloudflare 插件
如果使用 win-acme 的 Pluggable 版本,需要确认已经安装 Cloudflare DNS Validation Plugin。
例如插件文件可能位于:
1 | D:\Tools\win-acme\PKISharp.WACS.Plugins.ValidationPlugins.Cloudflare.dll |
启动:
1 | cd D:\Tools\win-acme |
如果插件加载正常,创建证书时就可以看到 Cloudflare DNS 验证方式。
五、使用 win-acme 创建新的证书任务
首先以管理员身份打开 CMD 或 PowerShell,然后启动 win-acme。
假设 win-acme 位于:
1 | D:\Tools\win-acme |
可以直接运行:
1 | D:\Tools\win-acme\wacs.exe |
也可以先进入目录:
1 | cd /d D:\Tools\win-acme |
启动后会看到类似:
1 | A simple Windows ACMEv2 client (WACS) |
这里可以先确认几件事情。
1 | Connection OK! |
表示 win-acme 已经能够正常连接 Let’s Encrypt 的 ACME 服务。
1 | Scheduled task looks healthy |
表示 win-acme 的自动续期计划任务目前正常。
如果还能看到:
1 | A: Manage renewals (1 total) |
说明当前机器已经存在至少一个旧的证书续期任务。
这不会影响创建新的通配符证书。
5.1 不要选默认模式 N,选择完整配置 M
主菜单中有:
1 | N: Create certificate (default settings) |
这里选择:
1 | M |
然后回车。
即:
1 | Please choose from the menu: M |
之所以不使用:
1 | N |
是因为这里需要自己明确控制:
1 | 域名 |
而 N 使用的是默认流程,更适合简单的网站自动配置。
对于:
1 | Windows |
这种组合,使用:
1 | M: Create certificate (full options) |
更合适。
5.2 选择证书目标来源
进入 Full options 后,win-acme 会询问证书的域名从哪里获取。
界面通常类似:
1 | Please specify how the list of domain names that will be included in the |
由于这里使用的是:
1 | Apache |
而不是 IIS,因此不能使用:
1 | Read bindings from IIS |
应该选择:
1 | 2 |
也就是:
1 | Manual input |
然后回车。
5.3 手动输入需要申请证书的域名
接下来 win-acme 会要求输入证书需要包含的域名。
例如可能出现:
1 | Enter comma-separated list of host names, starting with the common name: |
如果只需要通配符证书,可以填写:
1 | *.example.com |
但更推荐把根域名也一起放进去:
1 | example.com,*.example.com |
例如:
1 | Enter comma-separated list of host names, starting with the common name: |
注意多个域名之间使用:
1 | , |
也就是英文半角逗号分隔。
不要写成:
1 | example.com *.example.com |
也不要使用中文逗号。
推荐:
1 | example.com,*.example.com |
5.4 为什么建议同时申请根域名和通配符
单独申请:
1 | *.example.com |
只能覆盖一级子域名,例如:
1 | nas.example.com |
但是它不能覆盖:
1 | example.com |
因此如果未来根域名本身也可能建立网站,最好直接申请:
1 | example.com |
最终是一张包含两个 SAN 的证书:
1 | example.com |
这样以后就不需要再单独给根域名申请证书。
5.5 检查 win-acme 识别到的域名
输入完成后,win-acme 通常会显示识别到的域名,例如:
1 | Target generated using plugin Manual: example.com and 1 alternatives |
或者类似的信息。
这里一定检查是否同时包含:
1 | example.com |
如果发现:
1 | 拼写错误 |
不要继续。
直接退出本次创建任务并重新进入即可。
特别要注意:
1 | *.example.com |
最前面的:
1 | *. |
不能漏。
5.6 Common Name 如何处理
某些版本或配置下,win-acme 可能继续询问:
1 | Friendly name |
或者 Common Name 相关内容。
对于包含:
1 | example.com |
的证书,不需要过度关注传统的 Common Name。
现代 HTTPS 证书实际主要依赖:
1 | Subject Alternative Name |
也就是 SAN。
只要最后证书中的 SAN 包含:
1 | example.com |
即可。
如果 win-acme 自动使用第一个域名:
1 | example.com |
作为主名称,保持默认即可。
5.7 是否拆分成多张证书
后面的 Full options 流程中,win-acme 可能询问是否:
1 | Create separate certificates |
或询问如何拆分不同域名。
对于:
1 | example.com |
这种情况,我们的目标就是:
一张证书同时包含根域名和 wildcard。
因此不要拆成两张证书。
保持:
1 | Single certificate |
即可。
最终应该是:
1 | Certificate |
而不是:
1 | Certificate 1 |
5.8 到这里暂时不要继续乱选
完成域名输入以后,下一阶段才是真正重要的:
1 | 验证方式 |
也就是:
1 | HTTP-01 |
由于这里申请的是:
1 | *.example.com |
所以后面必须使用:
1 | DNS-01 |
并进一步选择:
1 | Cloudflare DNS |
不要选择:
1 | HTTP validation |
因为 Let’s Encrypt 的 wildcard certificate 必须通过 DNS-01 验证。
因此第五步做到这里,正确的状态应该是:
1 | 启动 win-acme |
下一节再配置:
1 | Cloudflare DNS-01 |
5.9 本步骤常见错误
错误一:选择 N
1 | N: Create certificate (default settings) |
虽然不一定完全不能用,但会让后面很多选项由 win-acme 自动决定。
本教程为了明确控制 Cloudflare DNS、PEM 输出以及 Apache reload,使用:
1 | M |
错误二:只输入普通子域名
例如:
1 | nas.example.com |
这样得到的仍然只是单域名证书。
如果目标是以后所有一级子域名共用一张证书,必须包含:
1 | *.example.com |
错误三:以为 wildcard 包含根域名
1 | *.example.com |
并不包括:
1 | example.com |
需要的话必须一起写:
1 | example.com,*.example.com |
错误四:使用 HTTP-01 验证 wildcard
后续如果看到:
1 | HTTP validation |
不要选择。
Wildcard certificate:
1 | *.example.com |
必须使用:
1 | DNS-01 |
错误五:把 Cloudflare 橙云关掉
申请 DNS-01 证书时,一般没有必要为了验证临时关闭 Cloudflare Proxy。
因为验证的是:
1 | _acme-challenge.example.com TXT |
不是访问:
1 | https://example.com |
因此:
1 | Proxied |
通常都不影响这一阶段的 DNS-01 验证。
六、使用 Cloudflare DNS-01 完成域名验证并导出 PEM 证书
完成上一节的域名输入后,win-acme 会询问如何证明自己拥有这些域名。因为这里申请的是通配符证书,所以必须选择 DNS-01;如果已经安装 Cloudflare 插件,菜单中会直接出现:
1 | 1: [http] Save verification files on (network) path |
这里选择:
1 | 6 |
即:
1 | [dns] Create verification records in Cloudflare DNS |
不要选择第 7 项手动创建 DNS 记录。虽然手动 DNS-01 也可以申请通配符证书,但 win-acme 已经明确提示:
1 | auto-renew not possible |
也就是说以后每次续期都需要人工修改 TXT 记录。使用 Cloudflare API 后,TXT 记录的创建和删除都可以自动完成,才能真正实现无人值守续期。
6.1 输入 Cloudflare API Token
随后会要求提供 Cloudflare API Token:
1 | Description: API Token for Cloudflare. |
第一次配置时选择:
1 | 1 |
然后粘贴上一节在 Cloudflare 创建的 API Token:
1 | Cloudflare API Token: ***************************************************** |
输入内容不会明文显示,这是正常现象。
win-acme 接着会询问是否把 Token 保存进自己的 Secret Vault:
1 | Save to vault for future reuse? (y/n*) - yes |
建议选择:
1 | y |
这样以后自动续期时不需要重新输入 Token。
随后给这个 Secret 设置一个便于识别的名称,例如:
1 | Please provide a unique name to reference this secret: |
这里建议名称也全部使用英文、数字和连字符,不要把真实域名、个人信息或者 API Token 本身写进名称。
例如:
1 | cloudflare-wildcard-token |
即可。
API Token 本身一定不要写进博客、截图或 Git 仓库。
6.2 私钥类型选择 RSA
域名验证方式配置完成后,win-acme 会询问证书使用什么类型的私钥:
1 | After ownership of the domain(s) has been proven, we will create a |
这里选择:
1 | 2 |
也就是:
1 | RSA key |
ECC 证书同样可以正常使用,而且密钥更短,但如果目标是兼容 Apache、各种客户端和旧设备,RSA 是最省心的选择。
因此本文统一使用:
1 | RSA |
6.3 将证书导出为 PEM 文件
证书签发后,win-acme 会询问保存方式:
1 | 1: IIS Central Certificate Store (.pfx per host) |
因为这里使用 Apache,所以选择:
1 | 2 |
即:
1 | PEM encoded files (Apache, nginx, etc.) |
然后 win-acme 会要求填写 .pem 文件的输出目录:
1 | Description: .pem files are exported to this folder. |
这里有一个实际踩过的坑:
证书保存路径不要包含中文字符。
例如下面这种路径不要使用:
1 | D:\Server\Apache24\conf\ssl\通配符 |
实际配置时 win-acme 可能直接报:
1 | Directory ... does not exist |
因此目录名统一使用 ASCII 字符,例如:
1 | D:\Server\Apache24\conf\ssl\wildcard |
本文后续统一使用:
1 | D:\Server\Apache24\conf\ssl\wildcard |
除了证书目录,整个 Apache、win-acme 和自动化脚本相关路径最好都不要使用中文,以免以后某个程序、脚本或计划任务对 Unicode 路径处理不一致。
6.4 PEM 私钥不设置密码
随后会询问是否给导出的 Private Key 设置密码:
1 | Description: Password to set for the private key .pem file. |
这里选择:
1 | 1 |
也就是:
1 | None |
原因不是“不加密码更安全”,而是 Apache 需要在 Windows 开机后自动启动。
如果给私钥设置密码,Apache 每次读取证书时可能需要人工输入密码,这会破坏:
1 | 开机自动启动 |
这套无人值守流程。
因此用于服务器自动运行的 PEM Private Key 通常保持无密码,然后通过 Windows 文件权限保护证书目录。
6.5 不需要再保存其他格式
第一次保存为 PEM 后,win-acme 会继续询问:
1 | 1: IIS Central Certificate Store (.pfx per host) |
如果只是给 Apache 使用,不需要再生成 PFX,也不需要额外导入 Windows Certificate Store,因此选择:
1 | 5 |
即:
1 | No (additional) store steps |
这样最终只维护 Apache 真正需要的 PEM 文件即可。
6.6 Installation Step 暂时选择 No
证书保存方式配置完成后,win-acme 会询问是否自动更新应用程序:
1 | Installation plugin IIS not available: No IIS sites detected. |
这里因为使用的是 Apache,不是 IIS,所以:
1 | Installation plugin IIS not available |
完全正常。
第一次配置时可以先选择:
1 | 3 |
即:
1 | No (additional) installation steps |
先确保:
1 | Cloudflare DNS 验证成功 |
之后再单独配置续期完成后自动执行:
1 | httpd.exe -k graceful |
这样排错更简单,不需要第一次申请时同时处理证书和 Apache 自动重载两个问题。
6.7 Cloudflare DNS-01 自动验证过程
完成以上配置后,win-acme 会正式向 Let’s Encrypt 创建订单。
例如:
1 | Plugin Manual generated source *.example.com with 2 identifiers |
如果上一节同时填写了:
1 | example.com,*.example.com |
那么:
1 | 2 identifiers |
是正常的,因为这张证书同时包含:
1 | example.com |
接下来 win-acme 会依次完成两个域名的 DNS-01 验证:
1 | [*.example.com] Authorizing... |
这里表示 win-acme 已经通过 Cloudflare API 自动创建了:
1 | _acme-challenge.example.com |
对应的 TXT 记录。
第一次查询时可能会看到:
1 | No TXT records found |
看到这里不要退出,也不要立刻去手动修改 Cloudflare DNS。
这通常只是:
1 | Cloudflare API 已创建记录 |
win-acme 会自动等待并重试。
正常情况下稍后会出现:
1 | Preliminary validation succeeded |
然后自动删除刚才创建的验证记录:
1 | Record ******** deleted |
如果证书同时包含根域名,接下来还会再执行一次:
1 | [example.com] Authorizing... |
这时候偶尔可能看到:
1 | Incorrect TXT record(s) found |
这同样不一定代表配置错误。
一个常见原因是前一个 DNS-01 challenge 的 TXT 记录刚刚删除,某些 DNS 节点缓存里暂时还保留旧值,而新的 challenge 已经创建。
只要随后出现:
1 | Preliminary validation succeeded |
就说明完全正常,无需人工干预。
6.8 判断证书是否申请成功
两个 identifier 都验证成功后,会看到:
1 | Downloading certificate [Manual] *.example.com |
其中最重要的是最后一行:
1 | Certificate [Manual] *.example.com created |
看到它就表示证书已经成功签发。
同时:
1 | Adding renewal for [Manual] *.example.com |
表示 win-acme 已经建立了自动续期配置。
主菜单中的 renewal 数量也会增加,例如原来:
1 | A: Manage renewals (1 total) |
申请成功后可能变成:
1 | A: Manage renewals (2 total) |
这说明新的 wildcard certificate 已经进入 win-acme 的 renewal 管理。
6.9 检查实际生成的 PEM 文件
申请成功后,先不要急着修改 Apache。
先检查:
1 | D:\Server\Apache24\conf\ssl\wildcard |
例如 CMD:
1 | dir D:\Server\Apache24\conf\ssl\wildcard |
win-acme 的 PEM Storage 通常会生成若干 .pem 文件,例如:
1 | example.com-chain-only.pem |
具体文件名以实际生成结果为准,不要在 Apache 配置中凭空猜文件名。
后续通常重点使用:
1 | *-chain.pem |
分别对应完整证书链和 Private Key。
6.10 本步骤最终选择速查
以后重新配置时,如果不想重新阅读整节,可以直接参考:
1 | Domain validation → 6 Cloudflare DNS |
成功标志:
1 | Authorization result: valid |
如果验证过程中只是短暂出现:
1 | No TXT records found |
或者:
1 | Incorrect TXT record(s) found |
但随后自动重试并出现:
1 | Preliminary validation succeeded |
则属于正常的 DNS 传播过程,不需要处理。
七、证书保存为 PEM
Apache 使用 PEM 文件比较方便。
在 win-acme 的证书存储方式中选择:
1 | PEM encoded files |
输出目录例如:
1 | D:\Tools\win-acme\certs |
生成后可能看到:
1 | D:\Tools\win-acme\certs\ |
Apache 通常主要使用:
1 | example.com-chain.pem |
实际文件名以 win-acme 输出结果为准。
可以查看:
1 | dir D:\Tools\win-acme\certs |
八、Apache 启用 SSL
打开:
1 | D:\Server\Apache24\conf\httpd.conf |
确认 SSL 模块已经加载:
1 | LoadModule ssl_module modules/mod_ssl.so |
如果还要做反向代理,通常还需要:
1 | LoadModule proxy_module modules/mod_proxy.so |
具体以自己的 Apache 环境为准。
九、Apache 使用 Let’s Encrypt 证书
假设证书文件为:
1 | D:\Tools\win-acme\certs\example.com-chain.pem |
Apache 可以这样配置:
1 | <VirtualHost *:443> |
注意 Windows 下 Apache 配置路径建议写成:
1 | D:/Tools/... |
而不是:
1 | D:\Tools\... |
十、一张通配符证书给多个子域名使用
假设后端服务分别运行在:
1 | 127.0.0.1:5000 |
则多个 VirtualHost 可以全部引用同一张通配符证书。
例如:
1 | <VirtualHost *:443> |
另一个服务:
1 | <VirtualHost *:443> |
再例如:
1 | <VirtualHost *:443> |
整体结构:
1 | Internet |
以后增加新服务时,只需要:
1 | 1. 添加 DNS |
不需要重新申请证书。
十一、反向代理时传递 HTTPS 信息
如果 Apache 前端是 HTTPS,而后端程序是 HTTP:
1 | 客户端 |
建议添加:
1 | RequestHeader set X-Forwarded-Proto "https" |
例如:
1 | <VirtualHost *:443> |
这样后端应用就更容易正确识别原始访问协议为 HTTPS。
十二、WebSocket 反向代理
部分面板、实时日志、终端等服务会使用 WebSocket。
如果普通页面正常,但实时功能异常,可以检查 WebSocket 代理。
例如:
1 | RewriteEngine On |
十三、测试 Apache 配置
每次修改 Apache 配置后,建议先测试语法:
1 | D:\Server\Apache24\bin\httpd.exe -t |
正常应该显示:
1 | Syntax OK |
确认无误后再平滑重新加载:
1 | D:\Server\Apache24\bin\httpd.exe -k graceful |
相比直接结束 Apache 进程,graceful 更适合重新加载配置和证书。
十四、配置证书自动续期后刷新 Apache
Let’s Encrypt 证书需要定期续期。
win-acme 会通过 Windows 计划任务自动检查证书。
但证书文件更新之后,Apache 也需要重新加载,否则运行中的 Apache 进程可能继续使用旧证书。
可以配置续期成功后执行:
1 | 程序: |
参数:
1 | -k graceful |
等价于:
1 | D:\Server\Apache24\bin\httpd.exe -k graceful |
完整续期流程:
1 | win-acme 定期检查 |
十五、检查自动续期任务
打开:
1 | Task Scheduler |
检查 win-acme 创建的 renewal task 是否存在。
也可以重新运行:
1 | D:\Tools\win-acme\wacs.exe |
进入 renewal 管理菜单查看现有证书。
如果之前还保留有旧的单域名证书 renewal,在确认所有 VirtualHost 都已经切换到 wildcard certificate 后,再决定是否删除旧 renewal。
十六、Cloudflare SSL 模式
如果某个子域名开启 Cloudflare 橙云代理:
1 | 客户端 |
推荐 Cloudflare 设置:
1 | SSL/TLS |
这样是:
1 | 客户端 |
Cloudflare 与客户端之间使用 Cloudflare Edge Certificate。
Cloudflare 与源站 Apache 之间,则使用服务器上的 Let’s Encrypt 证书。
两者不会冲突。
十七、Cloudflare 橙云和通配符证书不会冲突
例如:
1 | nas.example.com Proxied |
同时 Apache 使用:
1 | *.example.com |
完全没有问题。
橙云开启时:
1 | Browser |
灰云时:
1 | Browser |
所以服务器端配置正式受信任的通配符证书仍然很有意义。
十八、Let’s Encrypt 与 Cloudflare Origin Certificate
如果源站只允许 Cloudflare 访问,可以考虑 Cloudflare Origin Certificate。
但如果服务器既可能:
1 | 经过 Cloudflare |
又可能:
1 | 直接访问源站 |
更适合使用公开受信任的:
1 | Let's Encrypt |
Let’s Encrypt 可以同时用于:
1 | 浏览器 → Apache |
和:
1 | 浏览器 → Cloudflare → Apache |
因此通用性更高。
十九、检查当前证书
浏览器可以访问:
1 | https://nas.example.com |
查看地址栏中的证书信息。
如果开启 Cloudflare 橙云,浏览器看到的一般是 Cloudflare Edge Certificate,而不是 Apache 本机的 Let’s Encrypt 证书。
需要检查源站实际返回的证书时,可以使用 OpenSSL:
1 | openssl s_client -connect SERVER_IP:443 -servername nas.example.com |
重点检查:
1 | subject |
二十、常用排错命令
检查 Apache 配置:
1 | D:\Server\Apache24\bin\httpd.exe -t |
平滑重新加载:
1 | D:\Server\Apache24\bin\httpd.exe -k graceful |
查看 443 端口:
1 | netstat -ano | findstr ":443" |
查看证书目录:
1 | dir D:\Tools\win-acme\certs |
启动 win-acme:
1 | D:\Tools\win-acme\wacs.exe |
二十一、推荐架构
最终结构可以整理为:
1 | Internet |
外部统一访问:
1 | https://subdomain.example.com |
内部后端仍然可以使用:
1 | http://127.0.0.1:PORT |
HTTPS、证书、域名和反向代理全部集中交给 Apache 管理。
二十二、新增一个 HTTPS 服务
假设以后增加:
1 | media.example.com |
后端运行:
1 | 127.0.0.1:8000 |
先添加 DNS,然后配置 Apache:
1 | <VirtualHost *:443> |
检查:
1 | D:\Server\Apache24\bin\httpd.exe -t |
出现:
1 | Syntax OK |
再执行:
1 | D:\Server\Apache24\bin\httpd.exe -k graceful |
完成。
不需要:
1 | 重新申请证书 |
二十三、排错顺序
如果 HTTPS 突然异常,可以按照下面顺序检查:
1 | ① DNS 是否正确 |
如果只是新增:
1 | xxx.example.com |
并且已经有:
1 | *.example.com |
通常不需要重新申请证书。
总结
整套方案的核心是:
1 | Cloudflare |
第一次配置完成以后,后续维护成本很低。
尤其是配置好:
1 | Cloudflare API Token |
以后证书续期基本可以自动完成。
新增 Web 服务时,通常只需要:
1 | 添加 DNS |
就能直接使用 HTTPS。
