你们是不是也遇到过这种情况:Node.js 项目在本地npm start跑得好好的,一到 Windows 服务器上就不知道怎么“正规化”地跑起来。双击node app.js确实能起服务,但终端一关整个服务就没了,服务器重启一次又得手动拉起来,运维的人要是稍微马虎一点,线上就静悄悄挂了。
把 Node.js 部署到 IIS 上,恰恰是很多 .NET 团队、传统运维在 Windows 环境里最顺手也最刚需的方案。IIS 本身有进程托管、自动重启、日志、权限隔离、域名绑定这些现成能力,还能跟 Windows 认证、AD 域控配合,比裸跑一个node进程可控得多。但这个东西网上教程鱼龙混杂,要么太旧,要么只说一半,照着抄完还是 502、404、empty reply、PowerShell 执行策略报错……我前前后后在 Windows Server 2016/2019/2022 上都折腾过,这篇就把每个环节拆到最细,从装 Node.js 到建站点,到 web.config 逐行解释,再到部署后必踩的坑和排查链路,一次性讲清楚。
1. 为什么要用 IIS 托管 Node.js,而不是直接跑命令行
1.1 裸跑 Node 进程的四个痛点
很多人觉得 “IIS 是跑 ASP.NET 的,Node.js 自己就能监听端口,何必绕一圈”。这话在开发环境没问题,但到了生产环境,裸跑 Node 进程的缺点就非常明显。
首先是进程守护缺失。Node.js 进程一旦因为未捕获异常崩溃,没有人帮你把它拉起来。你要么写一个 Windows 计划任务定时检查端口,要么装 pm2 这种进程管理器,但这又多了一个要维护的组件。IIS 自带的应用程序池本身就具备进程回收、自动重启机制,进程死了它会按配置重新拉起 Worker Process。
其次是日志和监控分裂。Node 应用自己打的日志、IIS 的请求日志、Windows 事件日志,三套东西各管各的。排障时要来回切换上下文,效率很低。托管到 IIS 之后,所有的 HTTP 请求日志统一走 IIS,应用的 stdout/stderr 可以统一重定向到 iisnode 的日志目录,排查链路就顺了。
第三是端口管理和防火墙策略。裸跑 Node 通常监听 3000/8080 这种端口,和 IIS 的 80/443 是两套体系。运维要额外开防火墙端口,还容易和别人部署的服务端口冲突。用 iisnode 托管后,Node 应用不需要自己监听公网端口,IIS 直接通过命名管道把请求转发给 Node 进程,外部访问依然走 80/443,防火墙规则完全不用变。
第四是身份认证和 HTTS 证书体系的整合。如果你的内网环境有域控,用 Windows 集成认证来给内部系统做登录控制,裸跑 Node 就很难无缝对接。IIS 上可以做反向代理和 Windows 认证的组合,Node 应用本身不用实现认证逻辑,IIS 帮你全部挡在前面。
1.2 IIS 托管方案的三种主流技术路线
在 Windows 上把 Node.js 和 IIS 打通,主要有三种路线,部署前一定要先搞清区别,选错了后面全是坑。
路线一:iisnode 模块。这是最经典也是网上教程最多的一种。iisnode 是一个 IIS 原生模块,通过 web.config 里的handlers配置,把.js文件的请求直接交给 Node.js 进程处理。它最大的特点是 Node 进程由 IIS 直接管控,进程回收、并发控制都在这一个模块里完成。适合“一个 IIS 站点对应一个 Node 应用”的简单场景。
路线二:HttpPlatformHandler v2。这是微软官方接替 iisnode 的新方案,官方 GitHub 仓库现在还维护着。它不像 iisnode 那样直接映射.js文件,而是启动一个独立的 HTTP 进程,监听本地回环地址的随机端口,IIS 再把请求转发到这个端口。好处是它支持的运行时更广,不光是 Node.js,Java、Python 这种监听端口的进程都能托管。
路线三:ARR 反向代理。安装 IIS 的 URL Rewrite 和 Application Request Routing (ARR) 模块,把 https://你的域名/ 的所有请求反向代理到http://127.0.0.1:3000。这个方案对 Node 应用最“无感”,应用代码完全不需要感知 IIS 的存在,还是app.listen(3000)的方式。适合多个 Node 应用共用一个 IIS、各自监听不同端口的场景。
我个人的建议是:追求稳定文档多,用 iisnode;愿意折腾新版,用 HttpPlatformHandler;多站点各自为政,用 ARR。这篇的主体部分以 iisnode 为主线讲,最后再单开一节对比三个方案的选型细节。
2. 部署前的环境准备:从零装好 Node.js 与 IIS
2.1 Node.js 安装与 PATH 配置
不要为了图新鲜装最新的 Node.js。在 Windows Server 上跑生产环境,我建议用LTS 版本,而且版本不要过于激进,实测 Node.js 18.x 和 20.x LTS 对 iisnode 的兼容性最稳。有些 npm 依赖包在 Node 22+ 上会有原生模块编译问题,到时候排查起来很痛苦。
安装包直接去 Node.js 官网下 Windows Installer (.msi) 版本,安装过程中注意两点:
第一,安装路径建议保持默认的C:\Program Files\nodejs\。虽然自定义路径可以,但 iisnode 在解析 node.exe 路径时,对带空格的路径处理偶尔会出幺蛾子,后面 web.config 里写nodeProcessCommandLine也会更麻烦。保持默认最省心。
第二,安装完成后检查 PATH 环境变量。新版 Node.js 安装器会自动把C:\Program Files\nodejs\加到系统 PATH,但有些情况下(比如用绿色版、免安装版解压的)不会自动配置。安装完成后,打开一个新的 PowerShell 窗口,执行:
node -v npm -v如果提示“无法识别 node 命令”,就是 PATH 没配上。手动去“系统属性 -> 环境变量 -> Path”里添加C:\Program Files\nodejs\,然后重启 PowerShell。
2.2 启用 IIS 与必备模块
Windows Server 和 Windows 10/11 专业版都能装 IIS,但开启方式略有区别。
Windows Server 上打开“服务器管理器 -> 添加角色和功能”,勾选Web 服务器 (IIS)。注意在“角色服务”列表里,除了默认勾选的项,我强烈建议勾上这些:
- 常见 HTTP 功能 -> 默认文档、静态内容
- 运行状况和诊断 -> HTTP 日志记录、请求监视
- 安全性 -> URL 授权、Windows 身份验证(内网环境用得着)
- 管理工具 -> IIS 管理控制台
其中“静态内容”这一项很多人都忽略。部署的 Node 项目里如果有静态资源目录(比如public、uploads),IIS 默认不处理.js、.css、.png这些文件的静态请求,就会出现“样式全丢、接口正常”的诡异问题。
Windows 10/11 专业版则在“启用或关闭 Windows 功能”里勾选Internet Information Services,子项里的“万维网服务”记得全展开,把静态内容、默认文档这些也勾上。
IIS 装好后,还要装两个关键组件:URL Rewrite和iisnode。
URL Rewrite 是 iisnode 方案里做请求转发必需的——当用户访问https://你的域名/时,IIS 得知道把这个请求重写到哪个.js文件上。没有它,IIS 只会按默认规则找静态文件,找不到就直接 404。
URL Rewrite 的安装包可以从微软官网的 IIS 扩展页面下载。iisnode 呢,去 GitHub 上搜iisnode仓库的 Releases 页面,下载iisnode-full-v0.2.26-x64.msi这种安装包。虽然这个版本号停在 0.2.26 很久了,但实测在 Windows Server 2016/2019/2022 上配合 Node.js 18/20 都能正常工作。
注意:安装 iisnode 的时候,先装 URL Rewrite 再装 iisnode,这个顺序别搞反。iisnode 的安装程序会在 IIS 的全局配置里注册 handler,如果 URL Rewrite 还没装,后面手写 web.config 时又得回头补装,多绕一道弯路。
2.3 验证 iisnode 是否安装成功
装完之后,打开 IIS 管理器,左侧选中服务器节点,双击“模块”图标,往下翻能找到iisnode这个模块。如果能看见,说明模块注册成功。
更直接的验证方式是:在 IIS 默认站点C:\inetpub\wwwroot下新建一个测试文本文件,命名为test.js,在里面写:
var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello from Node.js on IIS'); }).listen(process.env.PORT);然后在这个目录下新建一个 web.config:
<configuration> <system.webServer> <handlers> <add name="iisnode" path="test.js" verb="*" modules="iisnode" /> </handlers> <rewrite> <rules> <rule name="test"> <match url="test.js" /> <action type="Rewrite" url="test.js" /> </rule> </rules> </rewrite> </system.webServer> </configuration>浏览器访问http://localhost/test.js,如果页面显示Hello from Node.js on IIS,说明 iisnode 已经能正常工作了,环境搭建这一步就算踩实了。
这里有一点要特别提醒:代码里的process.env.PORT是关键。iisnode 托管时不会让你的 Node 应用自己决定监听哪个端口,而是由 IIS 分配一个命名管道或者动态端口,通过环境变量PORT传给应用。如果你的应用里硬编码app.listen(3000),iisnode 在这一步就会出问题,后面我详细讲。
3. iisnode 方案的核心:web.config 按这个抄
3.1 一个可以直接套用的 web.config 完整示例
iisnode 方案里最核心的就是 web.config,它决定了 IIS 怎么把请求转给你写的 Node.js 代码。我直接把用了很多次的完整配置贴出来,然后逐段解释你才能知道每行是干嘛的,出了问题也好排查。
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <!-- 告诉 IIS:*.js 文件由 iisnode 模块处理,而不是当成静态文件 --> <handlers> <add name="iisnode" path="app.js" verb="*" modules="iisnode" /> </handlers> <!-- 告诉 IIS:把无法映射到静态资源的请求,重写到 Node.js 入口文件 --> <rewrite> <rules> <rule name="NodeApp" stopProcessing="true"> <match url="(.*)" /> <conditions> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> </conditions> <action type="Rewrite" url="app.js" /> </rule> </rules> </rewrite> <!-- iisnode 模块专项配置 --> <iisnode nodeProcessCommandLine=""C:\Program Files\nodejs\node.exe"" maxConcurrentRequestsPerProcess="1024" maxProcessCount="1" node_env="production" loggingEnabled="true" logDirectory="iisnode" debugHeaderEnabled="false" /> </system.webServer> </configuration>如果你的项目入口文件不叫app.js,比如是server.js或者index.js,把上面的app.js全部替换成实际文件名就行。
3.2 handlers 和 URL Rewrite 规则的逻辑拆解
很多人照抄 web.config 之后发现一个问题:直接访问http://你的站点/app.js能出结果,但访问http://你的站点/就是 404 或者空白。这就是没有理解 handlers 和 URL Rewrite 是两层东西。
handlers 这一层管的是“谁来处理这个请求”。path="app.js"的意思是只有请求路径精确匹配app.js时,iisnode 这个模块才会介入。但用户访问的 URL 千奇百怪:/、/api/user、/login……这些路径都不叫app.js,IIS 默认会把它们当作请求某个静态文件或目录去看,找不到就 404。
URL Rewrite 这一层管的是“请求进来之后往哪儿转”。我在配置里写的是一个通配规则:只要是既不是真实文件、也不是真实目录的请求(negate="true"就是“排除”的意思),一律重写到app.js。这样/、/api/user被 rewrite 成app.js之后,正好命中 handlers 里的规则,iisnode 模块接管,把请求交给 Node 进程处理。
这种“重写 + 处理器”的组合,本质上是在 IIS 和 Node.js 之间搭了一座桥。静态资源(图片、CSS、JS 文件)因为存在真实文件,IsFile条件判断为真,不会重写到 app.js,IIS 直接返回文件,性能更好;动态请求全部交给 Node,逻辑清晰。
3.3 iisnode 属性详解与 iisnode.yml
web.config 里<iisnode>标签的各个属性,建议不要全用默认值,尤其是这几个:
nodeProcessCommandLine:指定 node.exe 的绝对路径。注意 XML 转义,"要写成"。如果你的 Node.js 装在默认路径,按上面写就没问题;如果自定义路径,这里一定要改对,否则 iisnode 启动不了 Node 进程。maxConcurrentRequestsPerProcess:单个 Node 进程能同时处理的最大并发请求数。默认值不是很高,实测高并发下可以把等待队列拖死,调到1024比较稳妥。maxProcessCount:IIS 最多能启动几个 Node 进程。设成1表示单进程模式,避免多个进程共享内存状态(比如 socket.io 的广播)时出乱子。node_env:设成production会让 Express 等框架直接进入生产模式,从性能、报错信息两方面都更符合线上部署要求。loggingEnabled和logDirectory:打开 iisnode 自己的日志。排障的时候这些日志能救命的,后面我会专门讲怎么看。
如果你需要更细粒度的控制,比如修改日志文件最大大小、设置 devErrors 是否向前端暴露错误详情,可以在 web.config 的同级目录放一个iisnode.yml:
loggingEnabled: true logDirectory: iisnode debugHeaderEnabled: false devErrorsEnabled: true maxConcurrentRequestsPerProcess: 1024 maxProcessCount: 1iisnode.yml的优先级比 web.config 里的<iisnode>属性更高。也就是说俩都写了,yml 里的值会覆盖 XML 属性。这个机制有时候会让新手困惑:明明 web.config 里关闭了日志,日志目录里却还在产生文件,八成就是项目里残留了一个 iisnode.yml 在生效。
4. 创建 IIS 网站到跑通第一版的完整步骤
4.1 新建应用程序池并调优
环境准备好了,web.config 也放进项目目录了,接下来就是在 IIS 管理器里把站点正式建起来。
打开 IIS 管理器,左侧“应用程序池”右键 -> “添加应用程序池”。名称按项目名写,比如NodeAppPool,.NET CLR 版本选择“无托管代码”——很多人在这里习惯性选 v4.0,实际上 iisnode 跑的是 Node.js,不经过 .NET CLR,选“无托管代码”可以避免不必要的托管环境加载开销。
托管管道模式选“集成”。右键这个新建的应用程序池,进入“高级设置”,有两个参数建议改掉:
- “回收 -> 特定时间”:默认的应用程序池回收时间是凌晨 03:17(这个时间点是 IIS 随机生成的),一旦回收,正在处理的请求会被强杀。如果线上有正在跑的长任务,建议把特定时间清空,改为按需回收。
- “进程模型 -> 闲置超时(分钟)”:默认 20 分钟,也就是 20 分钟没有请求进来,这个 Node 进程就会被回收掉。很多 Node 应用第一次访问时特别慢,就是因为进程被回收后冷启动。如果对内存不敏感,把这个值设成
0(不回收),或者调大到 1440。
注意:这里的“回收”指的是应用程序池的工作进程(w3wp.exe)回收,iisnode 管理的 node.exe 子进程也会跟着受影响。如果发现 Node 应用“用着用着突然变慢、过一会又好了”,大概率就是定期回收导致的冷启动,先去检查这个设置。
4.2 网站绑定端口与主机名的细节
左侧“网站”右键 -> “添加网站”,站点名称随意,应用程序池选刚才建的NodeAppPool,物理路径指向你的项目发布目录。
绑定类型选http还是https看你的证书情况。端口这里有个常见的坑:如果你这台服务器上已经存在“默认网站”(Default Web Site)占用着 80 端口,你新建站点还绑定 80,启动站点时会直接报“另一个站点正在使用同一端口”。
解决办法有三种:
- 把默认网站停掉,让新站点独占 80。
- 给每个站点绑定不同的主机名,比如
node1.example.com和node2.example.com,IIS 按 Host 头区分请求。 - 让新站点用别的端口,比如 8080,访问时带端口。
我建议内网测试环境直接用端口区分,生产环境一定用主机头 + HTTPS 证书,这样最正规,也不会误伤同一台服务器上的其他站点。
绑定完之后,记得去 Windows 防火墙里确认入站规则是否放行了对应端口。如果是从外网访问,还要检查云服务器安全组规则——这个最容易漏,本地浏览器一访问直接超时,排查半天发现是云控制台的安全组没放行。
4.3 目录权限:从“应用程序池权限设置失败”说起
建完站点,直接把项目文件拷到物理路径,然后浏览器访问——这一步大概率会踩权限坑。常见报错是:
500.19 错误:无法读取配置文件
应用程序池权限设置失败,请手动为其设置 localsystem 权限 未知错误(0x80005000)
这个错误我在多个 Server 版本上都见过,本质原因就一个:应用程序池的工作进程身份没有权限读取你项目目录下的 web.config,或者是 IIS 管理器里 GUI 设置权限的功能在某些环境上存在缺陷,报了个让人摸不着头脑的错误码。
解决办法是右键点击项目目录 -> 属性 -> 安全 -> 编辑,给IIS_IUSRS用户组添加“读取和执行”、“列出文件夹内容”、“读取”权限。如果排障时急用,先给Everyone读取权限确认一下就是权限问题,然后再收紧成IIS_IUSRS。
如果项目里有写入需求(比如上传文件、生成日志),还要单独给对应的子目录(uploads/、logs/)加“修改”和“写入”权限。这里千万别图省事直接给整个项目目录 Everyone 完全控制,Node 的node_modules目录里第三方包数量巨大,一旦目录可写,被入侵后可能被直接篡改依赖,安全风险很高。
特别提醒:修改 web.config 后,IIS 会自动触发应用程序池回收,这会导致 Node 进程重启。如果你改了文件系统权限、环境变量、端口绑定这类不涉及 web.config 的配置,记得手动重启一下应用程序池,否则改动不生效,还容易误判成“改了没用”。
4.4 发布 Express 应用的注意点
把 Node 项目从开发机传到服务器上,我见的两种做法:一种是把整个项目文件夹(含 node_modules)直接拷贝过去;另一种是只拷贝源码,到服务器上重新npm install --production。
个人更推荐第二种。第一种虽然省事,但要是开发机和服务器系统版本不同(比如开发是 Windows,服务器是老 Windows),里面某些原生模块(bcrypt、sharp、canvas这类)可能是在开发机的 CPU 架构下编译的,拷过去直接报Invalid ELF header或者 “was compiled against a different Node.js version”。
正确做法是:
# 在服务器项目目录下执行 npm install --production --registry=https://registry.npmmirror.com用国内镜像源下载会快很多。装完依赖后,先别急着走 IIS,在项目目录里直接跑npm start或者node app.js,确认应用依赖没问题。这一步的意义是把“应用本身的错误”和“IIS 托管导致的错误”切开——应用本地跑不起来的,没必要让 IIS 背锅。
确认本地能跑之后,再把入口代码里写死的端口改掉。iisnode 模式下,应用必须监听process.env.PORT。Express 应用的经典写法:
const express = require('express'); const app = express(); // 你的中间件、路由... const port = process.env.PORT || 3000; app.listen(port, () => { console.log(`App listening on port ${port}`); });这段代码在本地开发时,process.env.PORT不存在,就会落在 3000;但 iisnode 托管时,环境变量里有 IIS 分配的动态端口,Node 进程监听这个端口,IIS 才能把请求正确地转发过去。
5. 部署后必踩的坑:从 502 到 empty reply 的排查链路
5.1 “npm 无法加载文件” —— PowerShell 执行策略
这是一个非常经典的 Windows 环境问题,虽然不完全发生在 IIS 部署环节,但只要是新服务器,十有八九会遇到。在 PowerShell 里执行npm install,提示:
npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。
这是因为 PowerShell 的默认执行策略是Restricted,不允许运行任何 .ps1 脚本,而 npm 的命令行入口是 npm.ps1。注意,node -v能通过是因为 node.exe 是原生程序,但 npm 是 PowerShell 脚本,所以被拦住了。
解决方法是在 PowerShell(管理员身份)里执行:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser选 “Y” 确认。RemoteSigned的意思是本地创建的脚本可以运行,从互联网下载的脚本需要数字签名——既满足了运行 npm 的需求,又不至于完全放开执行策略。
另外,如果是在cmd命令行里跑npm,则不会触发这个限制,因为 cmd 会直接调用npm.cmd。所以应急的时候切到 CMD 窗口也能绕过去,但根治还是要改执行策略,毕竟 PowerShell 才是 Windows 运维的主要工具。
5.2 访问根路径报 502.3 / empty reply,但访问具体路由却正常
这是我见过最多的一个坑。配置全部按教程做了,访问http://localhost/api/user正常返回 JSON,但访问http://localhost/时浏览器直接报502.3 Bad Gateway,或者empty reply from server。
问题几乎都出在 URL Rewrite 规则的匹配顺序和边界处理上。当用户访问/时,URL Rewrite 把它重写到app.js,但{REQUEST_FILENAME}这个条件在某些情况下判断/这个路径对应的物理目录是存在的(比如你的项目物理路径就是C:\inetpub\nodeapp,请求/时 IIS 认为请求的目标就是物理路径本身),于是IsDirectory条件成立,negate="true"取反后不满足重写条件,请求就停留在“请求一个目录”的状态,IIS 去找默认文档,找不到就 502.3 或者空响应。
解决办法有几个,按稳妥程度排序:
方案一:URL Rewrite 规则加上默认文档兜底。在重写规则之前,先加一条规则,当请求的目标是目录且存在默认文档时,直接重写到默认文档对应的应用入口。最省事的做法其实是让你的默认文档指向一个静态占位页,然后把所有非静态资源的请求全部重写:
<rule name="NodeApp" stopProcessing="true"> <match url="(.*)" /> <conditions> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> <add input="{URL}" pattern="^/$" negate="false" /> </conditions> <action type="Rewrite" url="app.js" /> </rule>其实就是把根路径/也纳入重写范围,不要让它命中 IsDirectory。
方案二:站点级别设置默认文档为app.js。在 IIS 管理器的“默认文档”里,添加app.js。这样当访问/时,IIS 先尝试找默认文档,找到app.js后,这个请求就变成静态文件请求app.js,再由 handlers 交给 iisnode。
注意:这里要理解 IIS 请求处理的顺序是 “先找默认文档 -> 再走 URL Rewrite -> 最后匹配 handlers”。方案二的思路是让默认文档把请求“升级”成对 app.js 的精确访问,从而绕过 IsDirectory 判断。实测这个方案对根路径 502 特别有效,而且不需要改动重写规则。
5.3 应用已经被 iisnode 启动,但请求还是 404
如果访问根目录报的是404 Not Found,而不是 502,那说明 iisnode 模块没有接管请求,或者接管了但 Node 进程没跑起来。这时候按顺序检查三件事:
第一,handlers 里的 path 是不是和入口文件名完全一致。大小写、多一个空格、拼写错误,都会导致匹配不上。IIS 的 handler 匹配是精确匹配,不像 Windows 文件系统不区分大小写那么宽松。
第二,URL Rewrite 模块是否真的装上了。在 IIS 管理器的站点“功能视图”里,看有没有“URL 重写”这个图标。没装 URL Rewrite 的话,web.config 里<rewrite>节点就是一个未知配置节,IIS 直接报 500.19 配置错误,根本到不了 404 这一步。
第三,Node 应用实际监听端口是否正常。在服务器上执行:
Get-Process node | Select-Object Id, ProcessName, Path看看有没有 node.exe 进程在跑。如果在跑,再确认它的监听端口(iisnode 模式是命名管道,可以用 Process Explorer 查看)。如果没有进程,打开日志目录看iisnode日志里的崩溃信息。
5.4 高并发下 Node 进程频繁崩溃重启
iisnode 方案在并发上来之后,Node 进程偶尔会崩,这是相对常见的问题。表现是:请求变慢、随机出现 503,过几秒自己恢复,反反复复。
排查思路分三步:
第一步,打开 iisnode 的崩溃日志,看节点进程是不是因为内存超限被回收。Node.js 的默认堆内存上限在 64 位系统上是 2GB 左右,如果你的应用频繁处理大对象,很容易触顶。可以在启动命令里加参数提高上限:
<iisnode nodeProcessCommandLine=""C:\Program Files\nodejs\node.exe" --max-old-space-size=4096" />第二步,检查是否有未捕获的 Promise 异常。Node 进程最怕的就是unhandledRejection,一旦全局未捕获异常出现,进程直接崩。在入口文件顶部加个兜底:
process.on('uncaughtException', (err) => { console.error('Uncaught Exception:', err); }); process.on('unhandledRejection', (reason) => { console.error('Unhandled Rejection:', reason); });这一对全局处理器不能替你解决业务逻辑问题,但至少能让进程扛住偶发的异常,而不是直接退出。日志打出来后,再针对具体错误修代码。
第三步,确认maxConcurrentRequestsPerProcess参数没卡住。iisnode 的并发模型是每个 Node 进程同时处理多个请求,如果单个请求阻塞时间太长,会让 Node 的事件循环失去响应。用kudu或者process.memoryUsage()打点监控,看是 CPU 密集导致的事件循环延迟,还是 I/O 等待过长。
5.5 日志不会说谎:iisnode 排障的正确打开方式
前面反复提到日志目录,这里把排障链路完整走一遍。iisnode 默认会在项目的 web.config 同级目录下生成iisnode文件夹,里面有这样几类文件:
| 文件类型 | 内容 | 排障用途 |
|---|---|---|
iisnode-xxxx-*.log | iisnode 模块自身的运行日志 | 模块加载、进程启动失败的原因 |
node-xxxx-*.log | Node 进程的 stdout/stderr 输出 | console.log、console.error打的内容全在这里 |
*.sock | 命名管道文件 | 存在即说明进程在监听,可忽略 |
遇到 502 或者 503,我的排查顺序永远固定:
- 先看
iisnode-*日志,里面会明确写“Failed to launch node executable”或“The node process did not start listening on the port”。这一条能直接定位是 node.exe 路径问题还是应用启动失败。 - 然后看
node-*日志,里面是应用自身打出来的错误堆栈,比如导入模块失败、端口被占用、数据库连接超时。 - 如果这两层都没有线索,再看 IIS 的“失败请求跟踪”日志(Failed Request Tracing),它能记录整个请求在 IIS 管线里经过了哪些模块、在哪一步出的错。开启方式是在站点功能视图里选择“失败请求跟踪”,添加规则,状态代码填
500-599,再设置跟踪文件的保存目录。
实测下来,90% 的部署问题通过前两步就能搞定,真正需要走到失败请求跟踪这层的情况并不多,但掌握它能让排查效率高一个量级。
6. 多站点场景的进阶方案:HttpPlatformHandler 与 ARR 选型
6.1 三种方案的核心差异对比
如果你的环境不止一个 Node 应用要放在 IIS 上,或者你的技术栈里还有 Java、Python 服务,那 iisnode 就不一定是最优解了。下面用表格对比三个方案,方便你按场景选型:
| 对比维度 | iisnode | HttpPlatformHandler v2 | ARR 反向代理 |
|---|---|---|---|
| 原理 | 模块内启动 node 进程,命名管道通信 | 启动 HTTP 进程,动态端口通信 | 反向代理到应用固定端口 |
| 应用代码改动 | 需改用process.env.PORT | 需改用process.env.PORT(v2 传HTTP_PLATFORM_PORT) | 无需改动,继续监听固定端口 |
| 多实例并发 | 单站点单进程为主 | 支持按进程数扩展 | 自行管理进程数 |
| 静态资源处理 | 由 IIS 统一处理 | 由 IIS 统一处理 | 可代理可自行处理 |
| 配置复杂度 | 中(web.config 配置项多) | 中 | 低,但需要额外维护端口进程 |
| 官方维护状态 | 基本停止更新 | 持续维护 | 持续维护 |
| 适合场景 | 老项目、单 Node 应用、已有大量教程参考 | 新项目、多运行时统一托管 | 多 Node 应用、不想改应用代码 |
6.2 我建议的选型思路
先说结论:新项目首选 HttpPlatformHandler v2,老项目稳定优先就用 iisnode,应用本身已经是微服务形态、多服务同时跑的话走 ARR。
HttpPlatformHandler v2 的配置思路是:在 web.config 的 handlers 里注册一个httpplatform:nodejs处理器,然后在<httpPlatform>节点里指定processPath和arguments,让它启动node app.js。这个模块会自己把环境变量HTTP_PLATFORM_PORT传给应用,应用监听这个端口即可。它比 iisnode 更接近“进程管理器”的形态,配置项也更语义化。
ARR 的方案最简单粗暴,装好 ARR 和 URL Rewrite 之后,web.config 里写一条反向代理规则:
<rewrite> <rules> <rule name="ReverseProxyToNode" stopProcessing="true"> <match url="(.*)" /> <action type="Rewrite" url="http://127.0.0.1:3000/{R:1}" /> </rule> </rules> </rewrite>Node 应用还像开发环境一样监听 3000 端口,配合 pm2 这类进程守护工具管理生命周期。这个方案的最大优势是应用代码零感知,迁移成本最低,你甚至可以同时代理多个端口对应多个应用,IIS 在这里纯粹当一个流量入口。
缺点也显而易见:Node 进程和 IIS 各管各的生命周期,监控、日志都割裂,需要自己额外做一层进程守护(pm2 或 NSSM),如果并发很高,本机回环代理也会损耗一些性能。
我个人在实际部署中一般是这么决策的:如果这台服务器的运维由 .NET 团队顺手管着,不希望引入太多额外组件,而且就是单个 Node 服务,就用 iisnode,它和 IIS 结合得最紧密,IIS 的回收、日志、权限体系直接接管 Node 进程。如果团队里有人对 pm2 很熟,或者同一个站点要同时代理好几个后端服务,ARR 更省事。
最后再分享一个小技巧:无论用哪种方案,先在本地把应用跑通,再上 IIS 托管,最后才配置域名和 HTTPS,这个顺序能帮你把“代码问题”和“托管问题”隔离开。真出问题了,先看应用自己的日志,再看 iisnode 或 HttpPlatformHandler 的日志,最后才翻 IIS 的配置——按这个链路走,没有排查不出来的问题。