502 Bad Gateway------部署 ASP.NET Core 应用时最常见的报错。IIS 明明跑着,Kestrel 也明明跑着,但请求就是过不去。这篇文章讲三个最容易配错的地方,每个都能让你 502。
大家好,我是码农刚子。
先说清楚架构。
IIS 后面挂 Kestrel,本质是IIS 做反向代理。用户请求到 IIS,IIS 通过 ASP.NET Core Module(ANCM)把请求转发给 Kestrel,Kestrel 执行你的应用代码,响应原路返回。
这条链路上任何一个环节断了,IIS 就返回 502。而三个配置错了,链路必断。
01 ANCM 没装或版本不匹配
这是 502.5 的头号元凶。
ANCM = ASP.NET Core Module。它是 IIS 和 Kestrel 之间的桥梁------没有它,IIS 不知道怎么启动 dotnet 进程,也不知道怎么转发请求。
两种情况会出问题:
① 服务器上没装 ANCM------你部署了应用,IIS 找不到模块,直接 502.5。这在全新服务器上最常见,装了 IIS 但忘了装 .NET Core Hosting Bundle。
② 装了但版本不匹配------你的应用 target 是 .NET 8,服务器上只有 .NET 6 的 runtime。ANCM 启动 dotnet 进程时找不到对应的 runtime,进程立即崩溃,IIS 收到 502.5。
怎么查?看 Windows 事件查看器:
应用程序日志 → 来源: IIS AspNetCore Module
错误: Could not find the assembly 'aspnetcorev2_outofprocess.dll'
看到这条就是模块没装对。
解法 :装 .NET Core Hosting Bundle (不是 SDK,是 Hosting Bundle)。它包含 ANCM + .NET Runtime,一条命令搞定。去微软官网下对应版本的 installer,装完 iisreset 一下。
⚠️ 坑中坑:升级 .NET 版本后,老版本的 ANCM 可能残留。检查 IIS 管理器 → 模块 → 看是否有重复的 AspNetCoreModuleV2,清理掉旧版本。
02 web.config 的 hostingModel 配错
这是最容易迷的坑,因为两种托管模式的配置完全不同。
ASP.NET Core 在 IIS 里有两种托管模式:
InProcess------不启动独立 Kestrel 进程,ANCM 直接在 IIS 工作进程(w3wp.exe)内托管应用。性能最好,但你的代码跑在 IIS 进程里。
OutOfProcess------ANCM 启动独立的 dotnet 进程(Kestrel),IIS 做反向代理转发请求。隔离性更好,但多一层转发开销。
配错的典型场景:你以为用的是 OutOfProcess,但 web.config 写了 InProcess,结果 ANCM 试图在进程内托管,但你的 Program.cs 里配了 Kestrel 特有的设置(比如 ListenUnixSocket),启动直接崩。
web.config 长这样:
xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*"
modules="AspNetCoreModuleV2"
resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
hostingModel="inprocess"
stdoutLogEnabled="true"
stdoutLogFile=".\logs\stdout" />
</system.webServer>
</configuration>
三个关键属性:
processPath------InProcess 模式下写 dotnet 或 .\MyApp.exe;OutOfProcess 模式下一样。但如果你的应用是 self-contained 部署,必须写 .\MyApp.exe,不能写 dotnet------因为服务器上可能没装 runtime。
arguments------framework-dependent 部署写 .\MyApp.dll;self-contained 不需要。
hostingModel------inprocess 或 outofprocess。.NET 6+ 默认 InProcess,但如果你显式写了 OutOfProcess,别又在 Program.cs 里用 IIS 专有 API。
💡 排查手段 :开启 stdout 日志------把
stdoutLogEnabled="true",确保 logs\stdout 目录存在且有 IIS 权限。502 时去看这个日志,启动错误信息全在里面。
03 请求转发超时和大小限制不一致
这个坑最阴------不是启动就 502,而是跑着跑着偶发 502。用户上传大文件时 502,或者长耗时接口偶发 502,其他请求正常。
原因是 IIS 和 Kestrel 各有各的限制,两层没对齐。
① 请求超时
IIS 侧:web.config 里 <aspNetCore requestTimeout="00:02:00" /> 默认 2 分钟。超了 IIS 直接断连,返回 502。
Kestrel 侧:KestrelServerOptions.Limits.KeepAliveTimeout 默认 130 秒。如果 IIS 的 requestTimeout 比 Kestrel 的 KeepAliveTimeout 长,会出现 IIS 还在等、Kestrel 已经关连接的情况------502。
对齐原则 :IIS 的 requestTimeout 必须小于 Kestrel 的 KeepAliveTimeout。留 10-20 秒余量。
② 请求体大小限制
IIS 侧:<requestLimits maxAllowedContentLength="30000000" />(约 28.6MB),在 system.webServer/security/requestFiltering 里配。
Kestrel 侧:KestrelServerOptions.Limits.MaxRequestBodySize 默认 30MB(~28.6MB)。
看起来差不多?但如果你的接口需要上传 100MB 文件,两层都要改。只改 Kestrel 不改 IIS,IIS 先拦住返回 413;只改 IIS 不改 Kestrel,Kestrel 拒绝,IIS 收到错误返回 502。
配置示例:
xml
<!-- IIS 侧:web.config -->
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="104857600" />
</requestFiltering>
</security>
<aspNetCore requestTimeout="00:05:00" ... />
csharp
// Kestrel 侧:Program.cs
builder.WebHost.ConfigureKestrel(opts =>
{
opts.Limits.MaxRequestBodySize = 104_857_600; // 100MB
opts.Limits.KeepAliveTimeout =
TimeSpan.FromMinutes(6); // > IIS requestTimeout
});
💡 记住这条线 :IIS 的限制 < Kestrel 的限制。让 IIS 先拦住,不要让请求过了 IIS 才被 Kestrel 拒------那样 IIS 只能返回 502,日志还不好查。
还有第三个阴坑 :MaxRequestBufferSize。Kestrel 默认 1MB 的请求缓冲区,如果你的请求 header 超过 1MB(比如带了一个超长的 cookie 或 token),Kestrel 直接拒绝连接,IIS 返回 502.3。解法是显式调大:
csharp
opts.Limits.MaxRequestBufferSize = null; // 不限制
opts.Limits.MaxRequestLineSize = 16_384; // 16KB
502 的排查,就三步: 看事件查看器 → 看 stdout 日志 → 对齐 IIS 和 Kestrel 的限制 日志在哪,问题就在哪。
收藏这篇,下次 502 不用猜。 三个配置对一遍,问题跑不掉。
关注 CSharp精选营,每周二四 get 能直接抄的 C# 实战。