ASP.NET Core部署到IIS报错HTTP Error 500.30与502.5的排查与解决方法(ANCM、stdout日志、应用池配置)

ASP.NET Core站点发布到IIS之后,浏览器一打开就是HTTP Error 500.30 - ANCM In-Process Start Failure。页面只有这一行字,IIS日志里也仅仅是一条500,看不出是谁炸的。这个状态码只说明一件事:进程被拉起来了,又立刻退出,退出原因IIS不知道。

要拿到真正的异常,得绕开IIS直接把程序跑一遍。


先在没有IIS参与的情况下启动

进到站点物理目录,命令行直接跑:

cd /d D:\wwwroot\apisite
dotnet ApiSite.dll

大部分问题在这一步就现形:连接串还是开发环境的值、appsettings里少了一段配置、监听端口被别的进程占了、运行时压根没装。控制台会把完整堆栈打出来,比那行500.30有用得多。

如果这里直接输出下面这段,说明服务器上缺ASP.NET Core运行时:

You must install or update .NET to run this application.
App: D:\wwwroot\apisite\ApiSite.dll
Architecture: x64
Framework: 'Microsoft.AspNetCore.App', version '8.0.0' (x64)

这个坑踩过不止一次:开发机上有SDK,一切正常;服务器上只装了SDK却照样报这个错,因为dotnet ApiSite.dll这种框架依赖的发布方式要的是运行时,SDK不算数。


装Hosting Bundle,别只装SDK

正确的做法是装对应版本的Hosting Bundle,安装包名形如dotnet-hosting-8.0.11-win.exe。它做三件事:装ASP.NET Core运行时、装ANCM(AspNetCoreModuleV2)、在IIS里把这个模块注册进去。装完必须重启IIS,模块不重启不生效:

net stop was /y
net start w3svc

装完确认一下运行时在不在:

dotnet --list-runtimes

输出里应该有Microsoft.AspNetCore.App那一行,版本要对得上目标框架。服务器上多个版本并存很正常,比如同时存在 .NET 6.0、7.0、8.0、9.0,只要应用指的那个在就行。


开stdout日志,让IIS把异常说出来

直接跑能定位,可有些问题只在IIS环境下才出现:权限、环境变量、应用池身份。这类情况把stdout日志打开,编辑站点根目录的web.config:

<aspNetCore processPath="dotnet" arguments=".\ApiSite.dll" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />

然后手动在站点目录下建logs文件夹,再给应用池身份写权限。目录不存在的话ANCM不会自动建,也不报错,结果就是日志开了却一个文件都没有,当初就是在这儿多花了半小时。


事件查看器里那几条ErrorCode

Windows日志、应用程序,来源IIS AspNetCore Module V2,典型记录长这样:

Application '/LM/W3SVC/2/ROOT' with physical root 'D:\wwwroot\apisite\' failed to start process with commandline 'dotnet .\ApiSite.dll', ErrorCode = '0x80004005'

几个ErrorCode值得记:0x80004005是进程起来了又自己崩,去看stdout日志;0x80070002是文件找不到,dll名或路径写错;0x80070005是权限不够,之前碰到过一次是站点目录整个放在了加密盘上,应用池身份读不出来。


processPath只写dotnet也可能解析不出来

IIS工作进程拿到的PATH跟命令行里不是同一份,processPath="dotnet" 有时候就是找不到。用where查真实路径:

where dotnet

把结果原样填进web.config:

<aspNetCore processPath="C:\Program Files\dotnet\dotnet.exe" arguments=".\ApiSite.dll" hostingModel="inprocess" />

这一招专治0x80070002。


inprocess模式下别开32位

hostingModel="inprocess" 时,应用池的"启用32位应用程序"必须是False,否则500.30就稳稳地挂在那儿。查和改都用appcmd:

%windir%\system32\inetsrv\appcmd list apppool
%windir%\system32\inetsrv\appcmd set apppool /apppool.name:apisite /enable32BitAppOnWin64:false

实在不想折腾,把hostingModel改成outofprocess也能绕过去,代价是性能差一点,进程回收的行为也不一样。


环境变量ASPNETCORE_ENVIRONMENT

不显式设置的话默认值是Production,于是appsettings.Production.json被读进来,里面的连接串往往还是占位符,应用一启动就抛异常退出。可以在应用池里设环境变量,也可以写进web.config:

<aspNetCore processPath="dotnet" arguments=".\ApiSite.dll" hostingModel="inprocess">
  <environmentVariables>
    <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" />
  </environmentVariables>
</aspNetCore>


覆盖dll之前先放app_offline.htm

站点在跑的时候ApiSite.dll是被占用的,直接覆盖要么失败,要么覆盖完了仍然跑旧代码。IIS认这个文件:往站点根目录放一个app_offline.htm,请求立刻被截断、应用进程退出,这时拷文件,拷完删掉它,站点自动恢复。这比重启应用池干净得多。


权限这条别漏

应用池身份默认是IIS AppPool\应用池名,这个虚拟账号需要在站点目录上有读取和执行权限,日志目录还要写入。图形界面里直接搜IIS AppPool\apisite是搜不到对象的,用icacls更快:

icacls D:\wwwroot\apisite /grant "IIS AppPool\apisite:(OI)(CI)RX" /T

排查顺序理一遍:dotnet ApiSite.dll直接跑看堆栈,dotnet --list-runtimes确认运行时,开stdout日志,再看事件查看器的ErrorCode。四步走完,500.30基本没有藏身之处。涉及版本 .NET 6.0、7.0、8.0、9.0,IIS 10.0,Windows Server 2016、2019、2022,数据截至2026年9月。