这篇文章记录了我在同一台 Windows Server 上用 IIS 为多个子域名各自配置 HTTPS 反向代理的完整过程。适用场景:服务器上跑着若干 Python/Node/其他 HTTP 服务,每个服务监听不同端口,希望对外统一用 HTTPS + 域名访问。
环境说明
- Windows Server(腾讯云轻量服务器)
- IIS 10,已安装 URL Rewrite 和 ARR 模块
- win-acme(
C:\win-acme\wacs.exe)负责 Let's Encrypt 证书 - 后端服务:FastAPI + uvicorn,监听
127.0.0.1:<port>
核心思路
IIS 支持 SNI(Server Name Indication),同一台服务器的 443 端口可以挂
多个不同域名的证书,互不冲突。每个子域名对应一个 IIS 站点,站点通过
web.config 中的 URL Rewrite 规则将流量转发到本地端口。
浏览器
│ HTTPS
▼
IIS (443) — SNI 按域名分发 —▶ 站点 A: tally.abelchan.top → 127.0.0.1:9000
站点 B: cps.abelchan.top → 127.0.0.1:8000
第一步:安装 IIS 模块
只需安装一次,后续添加新子域名跳过此步。
- URL Rewrite:写重定向规则(HTTP→HTTPS)
- ARR(Application Request Routing):实现反向代理
安装完 ARR 后,必须在全局启用代理功能,否则反代规则不生效:
%windir%\system32\inetsrv\appcmd.exe set config ^ -section:system.webServer/proxy /enabled:"True" /commit:apphost
第二步:申请 SSL 证书
win-acme 的 standalone 模式需要独占 80 端口做域名验证。如果 IIS 已经占用了 80 端口,改用 webroot 模式:在 IIS 对应站点 根目录下放置验证文件。
webroot 模式申请示例:
C:\win-acme\wacs.exe ^ --target manual --host tally.abelchan.top ^ --validation filesystem ^ --webroot C:\inetpub\tally ^ --store pemfiles --pemfilespath C:\nginx\ssl ^ --emailaddress you@example.com --accepttos
win-acme 同时注册任务计划自动续期,证书到期前会自动更新 PEM 文件。 但 Windows 证书存储中的 PFX 不会自动更新,需手动重新转换导入(见下)。
第三步:PEM → PFX → 导入证书存储
IIS 只认 Windows 证书存储,需先把 PEM 转 PFX 再导入:
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.serialization import pkcs12, load_pem_private_key
from cryptography import x509
import re
DOMAIN = 'tally.abelchan.top'
SSL_DIR = r'C:\nginx\ssl'
chain = open(f'{SSL_DIR}\\{DOMAIN}-chain.pem', 'rb').read()
key_data = open(f'{SSL_DIR}\\{DOMAIN}-key.pem', 'rb').read()
key = load_pem_private_key(key_data, password=None)
certs = [x509.load_pem_x509_certificate(c)
for c in re.findall(b'-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----',
chain, re.DOTALL)]
pfx = pkcs12.serialize_key_and_certificates(
name=DOMAIN.encode(), key=key,
cert=certs[0], cas=certs[1:] or None,
encryption_algorithm=serialization.NoEncryption(),
)
open(f'{SSL_DIR}\\{DOMAIN}.pfx', 'wb').write(pfx)
print('Done')
然后 PowerShell 导入并记录 Thumbprint:
$cert = Import-PfxCertificate `
-FilePath "C:\nginx\ssl\tally.abelchan.top.pfx" `
-CertStoreLocation Cert:\LocalMachine\My -Exportable
Write-Output $cert.Thumbprint
第四步:创建 IIS 站点
Import-Module WebAdministration $site = "tally.abelchan.top" $path = "C:\inetpub\tally" New-Item -Path $path -ItemType Directory -Force | Out-Null New-WebSite -Name $site -PhysicalPath $path -Port 80 -HostHeader $site -Force New-WebBinding -Name $site -Protocol "https" -Port 443 -HostHeader $site -SslFlags 1
第五步:绑定 SSL 证书(SNI)
用 hostnameport 形式绑定,支持同 IP 多 HTTPS 站点:
netsh http add sslcert ^
hostnameport=tally.abelchan.top:443 ^
certhash=<Thumbprint> ^
certstorename=MY ^
appid={A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
第六步:写 web.config 反代规则
在站点根目录(C:\inetpub\tally\)放置 web.config:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="HTTP to HTTPS" stopProcessing="true">
<match url="(.*)" />
<conditions><add input="{HTTPS}" pattern="^OFF$" /></conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}"
redirectType="Permanent" />
</rule>
<rule name="ReverseProxy" stopProcessing="true">
<match url="(.*)" />
<action type="Rewrite" url="http://127.0.0.1:9000/{R:1}" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
验证
# HTTPS 应返回后端响应 curl https://tally.abelchan.top/ # HTTP 应返回 301 跳转 curl -I http://tally.abelchan.top/
常见问题
| 现象 | 排查方向 |
|---|---|
| 502 Bad Gateway | 先检查后端端口是否在监听:netstat -ano | findstr :9000,若无则启动服务;若有则 iisreset /restart |
| HTTP 没有跳转到 HTTPS | 检查 URL Rewrite 模块是否安装,web.config 语法是否正确 |
| 证书过期 | win-acme 已更新 PEM 但 PFX 未更新,重新执行第 3 步转换并导入,再 iisreset |
| 新子域名申请证书报 80 端口被占 | 改用 webroot 模式,确保 IIS 对应站点已绑定该域名的 80 端口 |