在Windows Server生产环境部署Web服务,如何配置反向代理与进程守护
面向具备基础操作能力的运维人员,介绍在Windows Server中使用IIS、ARR、URL Rewrite与WinSW部署Web服务,涵盖回环端口、HTTPS证书、服务恢复、日志与资源边界,并提供分层验证、故障排查及安全回滚步骤。

生产环境的目标状态应当是:IIS 负责监听 80/443、终止 TLS 并将请求转发到本机应用端口;Web 应用以 Windows 服务运行,由服务控制管理器负责开机启动和失败恢复;应用端口只绑定到回环地址,不直接暴露给外部网络。
以下方案以 Windows Server 2019/2022/2025、IIS、URL Rewrite、Application Request Routing(ARR)和 WinSW 为例。操作前应核对具体系统版本、应用启动方式以及 URL Rewrite、ARR 与当前 IIS 的兼容性。ASP.NET Core 等原生支持 Windows 服务的应用,应优先使用框架自身的服务托管能力,不必额外套一层服务包装器。
先核对现状与影响范围
不要在生产服务器上直接安装组件、修改绑定或覆盖 web.config。先确认端口占用、现有 IIS 站点、证书和应用健康状态。
以管理员身份打开 PowerShell,执行:
Get-ComputerInfo |
Select-Object WindowsProductName, WindowsVersion, OsBuildNumber
Get-WindowsFeature Web-Server, Web-Mgmt-Console, Web-WebSockets
Get-Service W3SVC, WAS -ErrorAction SilentlyContinue
Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue |
Where-Object { $_.LocalPort -in 80, 443, 3000 } |
Select-Object LocalAddress, LocalPort, OwningProcess
Import-Module WebAdministration -ErrorAction SilentlyContinue
Get-Website
Get-WebBinding
其中 3000 只是本文的后端示例端口,应替换为应用的真实监听端口。检查结果需要回答以下问题:
- 80、443 是否已经被其他 IIS 站点或非 IIS 程序占用。
- 目标域名是否已有重复的主机名绑定。
- 后端应用能否独立启动并响应健康检查。
- 应用是否把上传文件、SQLite 数据库或运行时文件写在发布目录。
- 当前证书是否包含目标域名、私钥和完整证书链。
- 应用是否依赖 WebSocket、大文件上传或长时间请求。
- 服务器上是否存在其他共用 IIS 的业务。
如果后端已经运行,先绕过代理直接检查:
Test-NetConnection 127.0.0.1 -Port 3000
Invoke-WebRequest `
-Uri "http://127.0.0.1:3000/health" `
-UseBasicParsing
应用没有 /health 时,可以临时检查一个不会修改数据的普通页面。健康检查接口不应执行耗时查询,也不应返回密钥、连接字符串和详细异常。
变更前完成配置与文件备份
IIS 配置备份只覆盖 IIS 配置,不包含应用文件、证书私钥和数据库。它们需要分别处理。
先创建 IIS 全局配置备份:
$backupName = "Pre-WebProxy-{0}" -f (Get-Date -Format "yyyyMMdd-HHmmss")
& "$env:windir\System32\inetsrv\appcmd.exe" `
add backup $backupName
& "$env:windir\System32\inetsrv\appcmd.exe" `
list backups
如果目标站点已有 web.config,单独复制一份:
$backupDir = "C:\Backup\WebProxy\$(Get-Date -Format 'yyyyMMdd-HHmmss')"
New-Item -ItemType Directory -Path $backupDir -Force | Out-Null
Copy-Item `
-Path "C:\Sites\WebProxy\web.config" `
-Destination $backupDir `
-ErrorAction SilentlyContinue
应用发布文件建议采用版本目录,而不是直接覆盖正在运行的目录:
C:\Apps\MyWebApp\
├─ releases\
│ ├─ 20260910-1200\
│ └─ 20260912-1800\
├─ current\
├─ shared\
│ ├─ config\
│ ├─ logs\
│ └─ uploads\
└─ service\
配置、日志和上传文件放在 shared 中,可以避免版本回滚时被旧发布包覆盖。若应用包含数据库迁移,应另外制定数据库备份和向后兼容方案,不能把代码目录备份当作数据库回滚手段。
证书备份需要包含私钥,并保存在限制访问的安全位置。使用 PFX 时不要把密码写入脚本、工单或 Git 仓库。
让后端只监听本机地址
反向代理部署后,外部请求应只能到达 IIS。后端应用建议监听:
127.0.0.1:3000
不建议监听:
0.0.0.0:3000
具体配置方式取决于应用框架。例如 Node.js 应用需要在代码或环境变量中指定监听地址;ASP.NET Core 可以设置 URL;Java、Python 服务也应使用各自框架的绑定参数。
修改后再次检查:
Get-NetTCPConnection -State Listen |
Where-Object { $_.LocalPort -eq 3000 } |
Select-Object LocalAddress, LocalPort, OwningProcess
如果结果是 127.0.0.1 或 ::1,说明只接受本机连接。若结果是 0.0.0.0 或 ::,则应用仍在所有网卡上监听。此时不应仅依赖防火墙掩盖配置问题,而应优先修正应用监听地址。
将应用注册为受控 Windows 服务
进程守护不是简单写一个无限循环启动脚本。生产环境至少需要满足以下条件:
- 服务器启动后自动拉起应用。
- 应用进程异常退出后可以按策略重启。
- 停止服务时允许应用完成连接释放和日志写入。
- 服务使用低权限账户,而不是长期运行在管理员会话中。
- 标准输出、错误输出和应用日志能够滚动保存。
- 重启次数和退出原因可在服务状态、事件日志或监控中观察。
使用 WinSW 包装普通应用
从 WinSW 的正式发布渠道取得与系统架构匹配的程序,固定版本并校验文件来源。不同 WinSW 版本支持的配置项可能存在差异,应先在测试环境验证。
将可执行文件放入:
C:\Apps\MyWebApp\service\WebAppService.exe
并在同一目录创建同名配置文件:
C:\Apps\MyWebApp\service\WebAppService.xml
以下是 Node.js 应用示例,其他运行时需要替换 executable 和 arguments:
<service>
<id>WebApp</id>
<name>My Web Application</name>
<description>Production web application backend</description>
<executable>C:\Program Files\nodejs\node.exe</executable>
<arguments>C:\Apps\MyWebApp\current\server.js</arguments>
<workingdirectory>C:\Apps\MyWebApp\current</workingdirectory>
<env name="NODE_ENV" value="production" />
<env name="HOST" value="127.0.0.1" />
<env name="PORT" value="3000" />
<startmode>Automatic</startmode>
<delayedAutoStart>true</delayedAutoStart>
<stoptimeout>30 sec</stoptimeout>
<logpath>C:\Apps\MyWebApp\shared\logs\service</logpath>
<log mode="roll"></log>
</service>
不要把数据库密码、API 密钥和证书密码直接写进 XML。可根据应用能力使用受 ACL 保护的配置文件、Windows 凭据机制或企业密钥管理系统。
安装服务前,先验证应用命令可以在指定工作目录中正常启动。然后以管理员身份执行:
Set-Location "C:\Apps\MyWebApp\service"
.\WebAppService.exe install
.\WebAppService.exe start
Get-Service -Name WebApp
对于 ASP.NET Core 自包含发布,可以直接把 <executable> 指向应用的 .exe。如果程序已经按 Windows Service 模式开发,则直接使用其原生服务入口更合适。
设置服务失败恢复策略
WinSW 负责跟踪子进程,Windows 服务控制管理器负责失败恢复。下面的配置会在服务异常失败时分阶段重启:
sc.exe failure WebApp `
reset= 86400 `
actions= restart/5000/restart/15000/restart/60000
sc.exe failureflag WebApp 1
sc.exe qfailure WebApp
注意,失败恢复只能处理“进程退出”。如果应用进程仍在运行但已经死锁、线程池耗尽或无法响应请求,服务控制管理器不会自动发现。因此还需要外部健康检查和告警,不能把进程存在等同于业务可用。
使用低权限服务账户
默认使用 LocalSystem 会赋予应用过大的本机权限。更合理的做法是创建专用服务账户,并在“服务”管理器中为 WebApp 设置“登录”账户。
服务账户通常只需要:
- 应用发布目录的读取和执行权限。
- 日志、上传或缓存目录的修改权限。
- 访问后端数据库或网络资源所需的最小权限。
- “作为服务登录”的权限。
在授权前先查看现有 ACL:
icacls "C:\Apps\MyWebApp"
icacls "C:\Apps\MyWebApp\shared"
下面是授权形式示例,需要替换为实际账户,并根据目录用途分别授予权限:
icacls "C:\Apps\MyWebApp\current" `
/grant "DOMAIN\svc_web:(OI)(CI)RX"
icacls "C:\Apps\MyWebApp\shared\logs" `
/grant "DOMAIN\svc_web:(OI)(CI)M"
不要为了省事给应用目录授予 Everyone:F。修改服务登录账户后,应重新启动服务并检查事件查看器中的登录失败和权限错误。
安装并启用 IIS 反向代理组件
如果服务器尚未安装 IIS,可以通过 Windows Server PowerShell 添加角色:
Install-WindowsFeature `
Web-Server, Web-Mgmt-Console `
-IncludeManagementTools
只有业务需要 WebSocket 时,再添加对应功能:
Install-WindowsFeature Web-WebSockets
执行后检查结果中的 Restart Needed,不要在未确认影响范围时直接重启生产服务器。
随后安装与当前 IIS 兼容的 URL Rewrite 和 Application Request Routing。安装包应来自可核验的正式来源。完成后检查模块是否已载入:
& "$env:windir\System32\inetsrv\appcmd.exe" list modules |
Select-String "Rewrite|ApplicationRequestRouting"
在 IIS 管理器的服务器级别进入 Application Request Routing Cache → Server Proxy Settings,勾选 Enable proxy。如应用需要获取原始主机名,应启用保留主机头,或者通过受控转发头传递。
也可以在已经完成 IIS 备份的前提下执行:
& "$env:windir\System32\inetsrv\appcmd.exe" `
set config `
-section:system.webServer/proxy `
/enabled:"True" `
/preserveHostHeader:"True" `
/commit:apphost
这是服务器级配置,会影响同一 IIS 实例上的代理站点。共用 IIS 时必须先确认其他业务是否依赖不同设置。
创建独立代理站点
先检查目标名称和绑定不存在冲突,再创建站点目录、应用程序池和 HTTP 绑定:
Import-Module WebAdministration
New-Item `
-ItemType Directory `
-Path "C:\Sites\WebProxy" `
-Force | Out-Null
New-WebAppPool -Name "WebProxyPool"
Set-ItemProperty `
"IIS:\AppPools\WebProxyPool" `
-Name managedRuntimeVersion `
-Value ""
New-Website `
-Name "WebProxy" `
-PhysicalPath "C:\Sites\WebProxy" `
-ApplicationPool "WebProxyPool" `
-Protocol http `
-Port 80 `
-HostHeader "www.example.com"
将 www.example.com 替换为真实域名。若服务器已有同名站点或绑定,不要重复运行创建命令,应修改现有站点或使用新的变更方案。
在 C:\Sites\WebProxy\web.config 中写入:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Redirect 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="Reverse Proxy to WebApp" stopProcessing="true">
<match url="(.*)" />
<serverVariables>
<set name="HTTP_X_FORWARDED_PROTO" value="https" />
</serverVariables>
<action
type="Rewrite"
url="http://127.0.0.1:3000/{R:1}"
appendQueryString="true" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
该配置有两个动作:HTTP 请求先重定向到 HTTPS;进入 HTTPS 站点的请求再转发到本机 3000 端口。
使用 HTTP_X_FORWARDED_PROTO 前,需要在 IIS 管理器的服务器级 URL Rewrite 设置中,将它加入允许的服务器变量列表。否则站点可能因不允许设置该变量而返回 500 错误。
应用还需要正确识别代理头。启用“信任代理”时,应把信任范围限制在本机代理,而不是无条件信任来自公网的 X-Forwarded-For 或 X-Forwarded-Proto。后端只监听回环地址,正是建立这一信任边界的重要前提。
导入证书并建立 HTTPS 绑定
PFX 文件应包含证书私钥。导入前先确认目标域名、有效期和证书链。
$pfxPassword = Read-Host "请输入PFX密码" -AsSecureString
$cert = Import-PfxCertificate `
-FilePath "C:\Secure\www.example.com.pfx" `
-CertStoreLocation "Cert:\LocalMachine\My" `
-Password $pfxPassword
$cert |
Format-List Subject, Thumbprint, NotBefore, NotAfter, HasPrivateKey
确认 HasPrivateKey 为 True 后创建 SNI HTTPS 绑定:
Import-Module WebAdministration
New-WebBinding `
-Name "WebProxy" `
-Protocol https `
-Port 443 `
-HostHeader "www.example.com" `
-SslFlags 1
New-Item `
"IIS:\SslBindings\0.0.0.0!443!www.example.com" `
-Value $cert `
-SSLFlags 1
如果目标 Windows Server 上的 WebAdministration 提供程序行为不同,可以在 IIS 管理器的“站点 → 绑定”中完成证书关联。绑定前必须检查是否已存在相同 IP、端口和主机名组合,避免覆盖其他站点。
证书部署完成后还要记录续期负责人、到期告警和更换流程。仅导入新证书但没有更新 IIS 绑定,并不会自动完成证书切换。
日志、超时与资源边界怎么设置
生产环境至少要保留四类观察入口:
| 日志类型 | 常见位置或入口 | 主要用途 |
|---|---|---|
| IIS 访问日志 | C:\inetpub\logs\LogFiles\W3SVC* |
查看状态码、子状态码、请求耗时和客户端信息 |
| HTTP.sys 错误日志 | C:\Windows\System32\LogFiles\HTTPERR |
检查请求是否在进入 IIS 前被拒绝或中断 |
| Windows 事件日志 | 事件查看器的 Application、System | 检查服务退出、IIS、WAS、证书和权限错误 |
| 应用及服务包装日志 | C:\Apps\MyWebApp\shared\logs |
查看应用异常、启动失败和标准错误输出 |
日志必须配置滚动与保留策略。磁盘预算可以按以下公式估算:
日志预留空间 = 每日增长量 × 保留天数 × 安全系数
例如观察到日志每日约增长 1.5 GB,保留 14 天,安全系数取 1.3,则至少需要:
1.5 × 14 × 1.3 ≈ 27.3 GB
这只是计算示例,不是所有服务器的建议值。每日增长量应从本机实际日志变化中取得。
ARR 超时必须与应用行为一致。普通页面接口不应设置没有边界的长超时;文件导出、上传和 WebSocket 则不能照搬短请求配置。设置时需要满足:
- ARR 等待时间应覆盖应用正常请求时间,但不能无限放大。
- 应用自身超时应小于上游调用的整体超时预算。
- 数据库、外部 API 和代理不能各自无限重试。
- 上传大小限制要同时核对 IIS、应用框架和业务接口。
- WebSocket 业务需要安装 IIS WebSocket 功能,并验证连接升级与空闲超时。
资源边界也不能只看 IIS 应用程序池,因为真实业务进程运行在独立 Windows 服务中。
| 资源 | 建议观察项 | 达到边界时的处理方向 |
|---|---|---|
| CPU | 持续利用率、请求耗时、进程数 | 查找计算热点,避免用无限重启代替性能修复 |
| 内存 | 工作集、私有字节、提交量、分页 | 设置运行时合理上限,排查泄漏和大对象 |
| 磁盘 | 日志增长、临时文件、剩余空间、I/O 延迟 | 设置滚动、清理策略和容量告警 |
| 连接 | 活跃连接、队列、后端连接失败 | 核对线程池、连接池、代理超时和下游容量 |
| 重启次数 | 服务退出码、单位时间重启次数 | 触发告警,避免形成无休止重启循环 |
进程守护只能恢复进程,不能解决资源耗尽。服务连续重启时,应停止自动扩散影响并保留现场日志,而不是不断缩短重启间隔。
按层验证代理与守护是否生效
变更完成后按“后端进程、IIS 本机、HTTPS 域名、外部客户端”的顺序检查,便于快速定位失败发生在哪一层。
检查服务和后端端口
Get-Service WebApp
Get-NetTCPConnection -State Listen |
Where-Object { $_.LocalPort -eq 3000 }
Invoke-WebRequest `
-Uri "http://127.0.0.1:3000/health" `
-UseBasicParsing
成功条件是服务处于 Running,端口绑定到回环地址,健康检查返回预期状态。
检查 IIS 配置与绑定
Get-Website -Name "WebProxy"
Get-WebBinding -Name "WebProxy"
& "$env:windir\System32\inetsrv\appcmd.exe" `
list config "WebProxy"
不要为了让配置“生效”直接执行 iisreset。大部分 IIS 配置可以动态加载,iisreset 会影响同一服务器上的其他站点。确需重启应用程序池时,只操作目标池:
Restart-WebAppPool -Name "WebProxyPool"
在 DNS 切换前验证 HTTPS
系统存在 curl.exe 时,可以在服务器上将域名临时解析到本机,绕过正式 DNS:
curl.exe `
--resolve www.example.com:443:127.0.0.1 `
-I `
https://www.example.com/health
同时验证 HTTP 是否跳转:
curl.exe `
--resolve www.example.com:80:127.0.0.1 `
-I `
http://www.example.com/health
检查重点包括:
- HTTP 是否重定向到正确的 HTTPS 域名。
- HTTPS 证书是否匹配域名且证书链正常。
- 代理后的页面、静态资源和接口是否可用。
- 查询字符串是否完整传递。
- 应用生成的跳转链接是否仍为 HTTPS。
- 客户端 IP、主机名和协议识别是否符合预期。
- WebSocket、上传和长请求是否按业务需要工作。
验证失败恢复
直接停止 Windows 服务属于正常停止,通常不会触发“失败恢复”。如果需要验证崩溃恢复,应在维护窗口内先准确找到目标子进程,确认没有匹配到其他业务:
Get-CimInstance Win32_Process |
Where-Object {
$_.CommandLine -like "*C:\Apps\MyWebApp\current\server.js*"
} |
Select-Object ProcessId, Name, CommandLine
只有在确认 PID、允许短暂中断且已有回滚手段时,才可以终止该特定测试进程,再观察服务是否按策略恢复。不要使用模糊名称一次性结束服务器上的全部 node.exe、java.exe 或 python.exe。
常见失败对应的检查位置
| 现象 | 优先检查 | 常见原因 |
|---|---|---|
| IIS 返回 502 | 后端端口、WebApp 服务、应用日志、ARR 超时 | 应用未启动、监听地址错误、端口错误或后端超时 |
| IIS 返回 500 | IIS 日志、事件日志、web.config |
XML 格式错误、模块缺失、转发变量未加入允许列表 |
| 出现 HTTPS 重定向循环 | 应用代理信任设置、X-Forwarded-Proto |
应用始终认为原始请求是 HTTP |
| 域名返回错误站点 | IIS 绑定和 SNI 设置 | 主机名冲突、HTTPS 绑定未设置域名 |
| 证书不匹配 | HTTPS 绑定、证书主题和 SAN | 绑定了错误证书或访问域名不在证书中 |
| 服务启动后立即停止 | WinSW 日志、Application 日志、服务账户权限 | 启动命令错误、工作目录错误、配置缺失或无读取权限 |
| 服务运行但请求无响应 | 应用健康检查、线程和连接池、下游依赖 | 死锁、资源耗尽或数据库、外部接口阻塞 |
| WebSocket 频繁断开 | IIS WebSocket 功能、代理和应用日志 | 功能未安装、升级请求失败或空闲超时不匹配 |
处理 502 时,不要先重装 IIS。先直接请求 127.0.0.1:3000:如果直连也失败,问题在应用或服务;如果直连成功而代理失败,再检查 ARR、Rewrite、站点绑定和超时设置。
上线观察窗口与回滚触发条件
切流后应持续观察 IIS 状态码、后端健康检查、服务重启次数、CPU、内存、磁盘和应用错误。旧应用版本、旧代理配置和原证书绑定不要立即删除。
出现以下情况之一,应暂停继续扩大流量并评估回滚:
- 5xx 明显持续增加,且无法在观察窗口内定位。
- WebApp 服务进入连续重启状态。
- 应用健康检查间歇性失败或响应时间持续恶化。
- HTTPS 证书、域名跳转或登录回调异常。
- CPU、内存、磁盘或连接数触及既定资源边界。
- 新版本包含不可逆的数据结构变更,而兼容性尚未确认。
应用回滚时,先将服务切回上一个发布目录,再重启 WebApp 并验证本机健康检查。IIS 配置需要整体恢复时,可使用变更前创建的备份:
& "$env:windir\System32\inetsrv\appcmd.exe" list backups
确认备份名称和影响范围后,才执行恢复:
& "$env:windir\System32\inetsrv\appcmd.exe" `
restore backup "Pre-WebProxy-YYYYMMDD-HHMMSS"
该操作恢复的是 IIS 全局配置,可能撤销备份后其他站点的合法变更。共用 IIS 环境应优先恢复目标站点的 web.config、绑定和代理设置,不要未经核对直接恢复全局备份。回滚完成后仍需重新验证后端直连、HTTP 跳转、HTTPS 证书、代理请求和服务恢复状态,并保留故障期间的日志供后续分析。