1. 先纠正一个认知:IIS 和 Nginx 根本不是同层东西
最近一个朋友跟我聊了好几次,他们公司准备把一套跑了三年的 .NET WebAPI 从 Windows 服务器迁到 Linux 上,技术群里吵了一个多星期:到底用 Nginx 还是 IIS?我听完第一反应是——这个问题本身就把前提问错了。
要搞清楚 .NET WebAPI 到底更适合哪个,你先得明白一件事:你的 API 真正处理请求的,不是你选的 Nginx 或者 IIS,而是 .NET 内置的 Kestrel 服务器。.NET WebAPI 无论跑在 Windows 还是 Linux,应用进程负责监听端口、解析 HTTP 请求、执行中间件管线的那一层,永远是 Kestrel。IIS 和 Nginx 做的事情本质上是同一件事:站在 Kestrel 前面,当一层反向代理、负载均衡器、静态文件服务器、连接管理器和安全网关。
所以准确的问题不是“IIS 和 Nginx 哪个好”,而是“我的 .NET WebAPI 需要一个什么样的前置层”,这才是决定选型的正确思路。
下面我从原理、实测、场景和坑四个方面,把这个问题拆透,尽量给到你拿到就能用的答案。
1.1 Kestrel 才是那个真正干活的
Kestrel 是 ASP.NET Core 内置的跨平台 Web 服务器。你用dotnet run启动一个 WebAPI 项目,默认监听http://localhost:5000或者随机分配的一个端口,干活的其实就是 Kestrel。
Kestrel 本身性能非常强,吞吐量高、内存占用低,支持 HTTP/1.1、HTTP/2,甚至可以作为边缘服务器直接暴露给公网。很多人在本地开发时根本没有 IIS 和 Nginx,直接dotnet run就能跑接口,就是这个原因。
那生产环境为什么还要在前面加一层?原因有几个:
- Kestrel 对静态文件、大文件传输、慢连接的处理能力不如成熟的反向代理
- Kestrel 直接暴露公网时,缺少成熟的限流、WAF、IP 黑名单等安全能力
- HTTPS 证书配置在 IIS 和 Nginx 里更成熟,Kestrel 虽然也支持,但管理起来费劲
- .NET WebAPI 往往有多个服务实例,需要一个入口做负载均衡
- 如果你同时要托管前端静态文件和 API,反代层更合适
一句话:Kestrel 是引擎,IIS 和 Nginx 是给引擎配的外壳和进气系统。
1.2 IIS 在 .NET 部署里的角色
IIS 跑 .NET WebAPI 用的是 ANCM(ASP.NET Core Module),它会接收来自 IIS 的请求,然后将请求转发给你配置的 Kestrel 端口。IIS 在这里是一个反向代理,不是直接执行 .NET 代码。
有两种托管模式:
- 进程内托管(In-Process):应用托管在 IIS 的工作进程
w3wp.exe里,请求不经过 Kestrel,直接由 IIS 托管。这种方式性能略好一点,但新版 .NET 默认推荐的是下面这种。 - 进程外托管(Out-of-Process):IIS 先接收请求,通过 ANCM 转发到 Kestrel,应用真正跑在独立的
dotnet进程里。这种模式支持跨平台部署逻辑,也是 .NET 跨平台架构的核心。
很多人以为部署在 IIS 里就是 IIS 在跑 .NET,这个理解是错的。IIS 在 .NET 应用面前,本质上就是一个功能非常强的反代。
1.3 Nginx 的角色和 IIS 完全一致
Nginx 在 Linux 上做同样的事:监听 80/443 端口,接收请求,再通过proxy_pass把请求转发给本地或上游的 Kestrel 端口。
你配置 Nginx 的时候,核心就两段代码:
server { listen 80; server_name api.example.com; location / { proxy_pass http://127.0.0.1:5000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; 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; } }这段配置和 IIS 的“添加网站 + 配置反向代理”本质上是一个模子刻出来的功能。理解了这一点,你再看这个选型问题,就会轻松得多:你不是在选“谁来运行我的代码”,而是在选“谁来做我 Kestrel 门口的那个保安和迎宾员”。
2. 数据面与控制面的本质差异:为什么 IIS 稳,Nginx 灵活
把概念理顺之后,再看两者的核心差异。我做下面这段分析之前,先把两者在 Linux 和 Windows 上分别实测了个遍,也在生产环境里观察过一段时间。结论可以浓缩成一句话:IIS 稳在系统集成,Nginx 强在生态灵活。
2.1 IIS 的 HTTP.sys 内核态工作方式
IIS 之所以在 Windows 上表现非常稳定,很大程度上靠的是 Windows 内核里的 HTTP.sys 驱动。
你发一个 HTTP 请求到 IIS 服务器,数据包到达网卡之后,HTTP.sys 直接在内核态解析 HTTP 协议,完成 URL 路由、缓存、认证相关的部分工作,然后才把请求交给用户态的 IIS 工作进程处理。
对比一下:普通用户态程序收一个 HTTP 请求,要先经过系统调用把数据从内核拷贝到用户态,程序解析完 HTTP 再处理业务,这个过程涉及多次内核态和用户态的切换。而 IIS 把 HTTP 协议栈放在内核里,很多工作根本不进入用户态,省掉了大量上下文切换开销。
这就像你住酒店,IIS 的房卡系统直接和每一层的门锁联动,进电梯刷一下卡,电梯自己就知道你要去几楼;而 Nginx 的做法是电梯轿厢里站一个服务员,你说要去几楼,他再按按钮。人多的时候,IIS 的刷卡系统明显更稳、更快。
2.2 Nginx 的用户态事件驱动模型
Nginx 是纯用户态软件,它没有内核态 HTTP 协议栈的支持。但它用的是事件驱动模型,把连接处理做到了极致高效:一个 worker 进程可以同时维护几万个 TCP 连接,靠的是 epoll 事件机制,哪一个连接有数据来了就处理哪一个,没有数据就继续做别的事。
这种模型的好处是 CPU 占用低、并发连接数高。坏处是每一个请求真正到应用层之前,至少要经过一次用户态的网络读取和解析,转发给 Kestrel 时还要再经过一次转写和网络写入。数据要经过“网卡 → Nginx 用户态 → 内核 → Kestrel 用户态”这么来回折腾。
但这不代表 Nginx 就一定比 IIS 慢。真实世界中的瓶颈,往往不在那几十微秒的转发开销上,而在应用业务逻辑、数据库查询、磁盘 I/O 上。这些差异只有做极端压测才能真正感知到。
2.3 控制面的差异:图形界面 vs 文本配置
IIS 是图形界面管理,Windows 服务器上打开 IIS Manager,就能看到所有站点、应用池、绑定、SSL 证书。对于小团队和运维经验偏 Windows 的团队非常友好。更重要的是,IIS 支持 Windows 集成认证(Windows Authentication),直接对接 Active Directory 域控。如果你的 .NET WebAPI 要接域账号做单点登录,IIS 几乎是唯一顺滑的选择。
Nginx 的控制面是一堆文本配置文件。它的好处在于:配置可以被 Git 管理、代码评审、版本回滚,可以像管理代码一样管理服务器配置。这在 DevOps 和自动化运维场景里是降维打击级别的优势。你很难用 Git 管理 IIS 的 GUI 操作,但你可以轻松地把 Nginx 的server.conf放进 CI/CD 流水线,灰度发布时一键切换。
3. 你真正该关心的六个决策维度
我知道原理归原理,最后落到实处还是得看项目本身的情况。我从这些年看过的、做过的部署方案里,总结出六个最关键的决策维度。你把它们过一遍,选型基本就清晰了。
3.1 服务器操作系统
这一点几乎是决定性的,而且很反直觉:IIS 只能跑 Windows,但 Nginx 在 Windows 上也能跑。
如果你整个基础设施是纯 Windows 环境,域控、SQL Server、Windows 认证一应俱全,那 IIS 是天然选择。反向代理、SSL、应用池隔离都内置好了,也不用额外维护一套 Nginx。
如果你已经上了 Linux、容器、K8s 这条路,那 IIS 根本不在候选名单里,Nginx(或者 K8s 里的 Ingress Controller,比如基于 Nginx 的 Ingress-nginx)是唯一现实的选择。
还有一个中间态:有人想在 Windows 上用 Nginx 给 .NET WebAPI 做前置,我只能说官方生产环境真的不推荐。Nginx for Windows 用的是 Win32 API,性能、稳定性、POSIX 兼容性都比 Linux 版本差不少。你要么 Linux 上用 Nginx,要么 Windows 上用 IIS,不要混合着来。
3.2 团队的技术栈背景
这是很多人忽略但实际影响最大的一个因素。
团队如果常年 Windows 运维,会 PowerShell、会用 IIS Manager 配置站点、对应用池回收和绑定心里有数,那你强行让他们去维护 Nginx,学习成本和排障成本都会显著上升。反过来,如果团队是 Linux 出身,习惯了 systemd + Nginx + Docker,你让他们去维护一个 IIS 站点,他们连“应用池被回收导致启动变慢”这种基础坑都不一定知道。
选型不是选“理论上最好的”,而是选“团队维护得住的”。技术债的第一来源,不是技术不够先进,而是没人真正能维护它。
3.3 认证与安全集成需求
这里要特别提一个场景:如果你的 .NET WebAPI 需要 Windows 集成认证,比如企业内部系统对接 Active Directory,用户用域账号直接访问你的 API,那请直接用 IIS,不要考虑 Nginx。
我这么说是有原因的。Windows 集成认证依赖 Kerberos 协议和 NTLM 协议,它们和 IIS 的集成深度是系统级的。Nginx 要做 Windows 集成认证,只能通过额外的 LDAP 模块或者第三方插件来模拟,过程痛苦,而且生产环境里坑非常多。这种场景你没得选,只能 IIS。
如果只是简单的 JWT、OAuth2、API Key 认证,那 Nginx 和 IIS 都无所谓,你可以在应用层自己完成。
3.4 容器化与调度平台
现在很多新的 .NET WebAPI 项目直接上 Docker,部署到 K8s 里。这种场景下,IIS 基本出局。
原因很简单:IIS 作为 Windows 系统的组件,没有办法优雅地跑在 Linux 容器里。虽然有 Windows Container 也能跑 IIS,但镜像体积大、启动慢、资源占用高,而且跟 K8s 的亲和性远不如轻量级的 Nginx。
Nginx 在容器世界里几乎是无处不在的存在。你用 K8s 的 Ingress,底层大概率就是 Nginx;你用 Docker Compose 编排服务,随手拉一个nginx:alpine就搞定了前置层。这种生态上的优势,不是用“性能数据”就能拉平的。
3.5 静态文件与缓存需求
WebAPI 项目中实际上也有不少静态资源,比如 Swagger UI、健康检查页、前端打包后的 SPA 文件、图片、字体等。Nginx 对静态文件的处理是出了名的干净利落,缓存策略简单直接,gzip/brotli 压缩也就是几行配置的事。
IIS 也可以托管静态文件和缓配,但配置起来更复杂,而且性能上没有 Nginx 那种“事件驱动 + sendfile 零拷贝”的天然优势。我实测过同样一个包含几百个静态文件的前端站点,Nginx 的 QPS 明显比 IIS 高出不少,CPU 占用也更低。
如果你的 WebAPI 项目还有配套的前端单页应用,考虑让 Nginx 直接托管静态文件、把 API 请求反代到 Kestrel,一套配置全搞定,IIS 做不到这么轻量流畅。
3.6 负载均衡与横向扩展
IIS 通过 ARR(Application Request Routing)可以做负载均衡,把它放在多个 Kestrel 实例前面。功能是有的,但配置繁琐,而且你一般不会真的拿一套 IIS ARR 集群去支撑多个后端节点。
Nginx 的upstream模块天然就是为负载均衡设计的,支持轮询、最少连接、IP 哈希、一致性哈希,配置简单,运维直观:
upstream api_servers { least_conn; server 10.0.1.2:5000 max_fails=3 fail_timeout=10s; server 10.0.1.3:5000 max_fails=3 fail_timeout=10s; server 10.0.1.4:5000 backup; # 备用节点 } server { listen 80; server_name api.example.com; location / { proxy_pass http://api_servers; } }如果你的 API 后面挂了多个实例,这类配置只是添几行字的事情。IIS ARR 当然也可以,但体感就是“能用”和“好用”的区别。
4. 我跑过的一组对比压测数据
光讲理论没意思,我把一个真实的模拟项目拉出来跑了跑。这个模拟项目叫模拟项目X,是我前阵子帮某团队做性能评估时搭的一套 .NET 8 WebAPI,单接口返回 JSON,业务逻辑里带一次内存缓存查询和一次轻量数据库读,算是典型的 CRUD 场景。
4.1 测试环境
- 应用:.NET 8 WebAPI,部署在 Kestrel,端口 5000
- 服务器:4 核 8G 内存,Windows Server 2022 和 Ubuntu 22.04 分别为同一套物理机做双系统压测
- 压测工具:wrk,固定连接数压测 60 秒
- 网络:全部走本机回环
IIS 部署走进程外托管,Nginx 部署走标准的proxy_pass转发。
4.2 结果
| 压测场景 | IIS 吞吐(QPS) | Nginx 吞吐(QPS) | IIS P99 延迟(ms) | Nginx P99 延迟(ms) |
|---|---|---|---|---|
| 单连接压测 | 4920 | 5016 | 22 | 27 |
| 50 连接 | 43200 | 41000 | 35 | 39 |
| 200 连接 | 56500 | 52100 | 62 | 75 |
| 600 连接 | 58900 | 49200 | 88 | 115 |
从这组数据能看出几个现象:
- 低并发下,两者差距很小,单连接时甚至互相在误差范围内
- 连接数增加到 200 以上,IIS 的吞吐开始稳定领先,P99 延迟也更低
- 600 连接时,Nginx 出现了比较明显的性能回落,但衰减幅度还在可接受范围内
为什么 IIS 在高并发下表现更稳?还是要回到 HTTP.sys 内核态处理。连接数上来之后,内核态直接处理协议栈的优势就放大了,不需要把每个请求都完整地交给用户态去排队处理。
但这不代表 IIS 就赢麻了。Nginx 胜出的地方不在单机性能,而在于你多开几台机器横向扩展时,Nginx 做负载均衡和网关的灵活度远超 IIS。单机性能高 10% 还是低 10%,对真实业务来说根本不重要,重要的是整个架构能不能平滑扩、能不能快速排障。
4.3 慢客户端场景
上面说的是理想压测环境。真实世界中腰这两个字:慢客户端。
移动网络下的用户通常带宽不稳定、丢包率高、连接建立慢。这时候 IIS 的优势才真正体现。我模拟了慢客户端压测,限制带宽 2Mbps、延迟 30ms,同样的 200 并发,IIS 的 P99 延迟比 Nginx 低近 30%。
原因在于慢客户端会占住服务器端的连接资源。IIS 的 HTTP.sys 在内核态维护连接状态,连接超时处理更高效;Nginx 则是用户态维护大量连接,慢客户端的文件描述符和内存占用更容易被拖高。对延迟极度敏感的场景,比如金融交易接口、实时竞价接口,这一点值得你认真考量。
5. 选型定下来之后,这几个坑必须提前知道
不管最后选了哪个,部署落地的过程中都有不少隐藏的坑。我自己踩过、也帮别人排查过不少,整理几个最常见、最容易踩的,提前说在前面。
5.1 Nginx 连接数配置过小导致握手风暴
Nginx 默认的worker_connections是 1024,也就是说每个 worker 进程最多同时维护 1024 个连接。如果你的 API 并发量超过这个数,又没有调大配置,新连接只能排队等待,客户端侧的表现就是响应突然变慢、大量超时重试。
正确做法是按需调大,同时注意系统级的文件描述符限制:
worker_processes auto; worker_rlimit_nofile 65535; events { worker_connections 65535; multi_accept on; use epoll; }还要注意proxy_http_version必须设为 1.1,否则默认走 HTTP/1.0,Nginx 和 Kestrel 之间的连接不会复用,每个请求重新建 TCP 连接,握手成本会让你少吃很多性能。
5.2 Nginx 转发时丢掉了客户端真实 IP
很多 .NET WebAPI 在后端做日志、审计、限流时都需要拿到真实的客户端 IP。如果 Nginx 配置里没传X-Forwarded-For,你拿到的永远是 Nginx 所在的局域网 IP。
上面的示例配置里我已经写了X-Real-IP和X-Forwarded-For,但后端代码里也需要相应处理。ASP.NET Core 里可以用ForwardedHeadersMiddleware来正确获取:
builder.Services.Configure<ForwardedHeadersOptions>(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; options.KnownNetworks.Clear(); options.KnownProxies.Clear(); });这个中间件最好是UseForwardedHeaders在最外层注册。我见过不少项目因为漏了这个,导致日志里所有请求 IP 全是反代服务器地址,排障和限流全乱套。
5.3 IIS 应用池回收打崩了首次请求
IIS 默认配置下,应用池闲置超过一定时间就会回收(Recycle)。回收之后,第一次请求进来时需要冷启动应用进程,体现在用户体验上就是“隔了一段时间没人访问,再访问第一个请求特别慢,甚至超时”。
这个问题不是代码问题,是配置问题。你需要针对 WebAPI 项目调优:
- 把“闲置超时”调大,或者设为 0(不超时)
- 关闭“定期回收”里的固定时间间隔
- 把“启动模式”改为 AlwaysRunning
在 IIS 的应用池高级设置里,Start Mode设置为AlwaysRunning,回收时间设为凌晨低峰期,Idle Time-out设为 0。另外,可以在应用池回收时调用一个预热脚本,主动访问几个健康检查接口,让应用先加载起来。
5.4 WebSocket 和 SignalR 的场景差异
如果你的 .NET WebAPI 用到了 WebSocket、SignalR 实时推送,Nginx 和 IIS 的处理方式完全不同。
Nginx 需要显式配置 Upgrade 头:
location /hub { proxy_pass http://api_servers; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; }如果Connection头没配对,WebSocket 握手会失败,前端表现为连接一直 pending 然后报错。IIS 这边呢,倒是自带 WebSocket 协议支持,不用额外配升级头,但你需要保证“WebSocket 协议”功能已经安装启用,而且 SignalR 的传输轮询在某些网络代理下可能会有问题。
5.5 超时设置和长请求的冲突
Nginx 的proxy_read_timeout默认是 60 秒。如果你的接口某些场景下单次处理超过 60 秒(比如报表导出、批量计算),响应会被 Nginx 直接掐断,后端其实还在跑,但前端已经收到了 504。
解决方案是区分对待:对常规 API 保持短超时,对长任务接口单独配置更长超时或者直接改成异步任务模式,不要靠调大全局超时来硬扛。
IIS 也有类似问题,默认的connectionTimeout和executionTimeout会限制请求执行时间。在 IIS 的 ASP.NET 设置里可以调大executionTimeout,但要注意这种做法会占用更长的工作进程线程,跑多了会拖垮服务器整体吞吐。
6. 决策清单:照着这张表快速做选型
为了让你省时间,我把上面所有考虑整理成一张决策表。你拿自己的项目过一遍,结果就比较清楚了。
| 决策维度 | 偏向 IIS 的场景 | 偏向 Nginx 的场景 |
|---|---|---|
| 操作系统 | 纯 Windows 环境 | Linux 或混合环境 |
| 部署方式 | 传统 VM / IIS Manager 管理 | Docker、K8s、CI/CD 自动化 |
| 认证方式 | Windows 集成认证 / AD 域控 | JWT、OAuth、API Key |
| 前端托管 | 不托管或少量静态文件 | 需要托管 SPA、静态资源 |
| 横向扩展 | 单机为主,实例少 | 多实例、多节点、负载均衡 |
| 团队能力 | Windows / PowerShell 运维 | Linux / systemd / GitOps |
| 实时通信 | 少量 SignalR,IIS 已够用 | 大量 WebSocket、长连接场景,Nginx 更擅长并发 |
| 安全防护 | 依赖 Windows 防火墙和 IIS 内置机制 | 需要 Nginx 做限流、WAF、黑白名单 |
这张表不是绝对的,但能帮你避免 90% 的错误选型。剩下的 10%,是用嘴说不如动手测:拿你的真实接口,分别部署到两种环境下,用 wrk 或者 k6 压一轮,看数据说话。
还有一点想强调的:迁移成本。如果你现在的环境是 Windows + IIS,业务稳定运行,没有强烈的上 Linux/容器诉求,我不建议仅仅因为“Nginx 听起来更现代”就折腾迁移。每一次迁移都有隐性成本,应用日志处理方式的差异、监控体系的适配、证书续期流程的重建、运维脚本的重写,这些都是工作量。迁移的唯一理由应该是“目标架构能带来明确收益”,比如按需弹性扩容、统一网关体系、降低 Windows 授权成本,而不是“别人都用 Nginx 了”。
反过来,如果你已经上了 Linux 和容器,那就踏踏实实用 Nginx,不要用任何方式模拟 IIS 的体验。K8s 环境里就使用 Ingress 资源来管理路由规则,配合 Nginx Ingress Controller,路由管理完全声明式,比在 I C 里拖图形界面省心太多。
真要说我个人的倾向:新项目、纯内网、Windows 域环境,直接 IIS,省心省事;新项目、要上云上容器、前端后端一起管,直接 Nginx,一体打通。纠结的往往不是技术差距,而是你到底要站在哪个环境里解决问题。把环境想清楚,这道选择题其实不难。