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 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。

配置示例:

<!-- IIS 侧:web.config -->
<security>
  <requestFiltering>
    <requestLimits maxAllowedContentLength="104857600" />
  </requestFiltering>
</security>

<aspNetCore requestTimeout="00:05:00" ... />
// 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。解法是显式调大:

opts.Limits.MaxRequestBufferSize = null; // 不限制
opts.Limits.MaxRequestLineSize = 16_384; // 16KB

502 的排查,就三步: 看事件查看器 → 看 stdout 日志 → 对齐 IIS 和 Kestrel 的限制 日志在哪,问题就在哪。


收藏这篇,下次 502 不用猜。 三个配置对一遍,问题跑不掉。

关注 CSharp精选营,每周二四 get 能直接抄的 C# 实战。