Исправление: HTTP Error 500.30 - ASP.NET Core app failed to start после развёртывания на IIS
500.30 означает, что приложение выбросило исключение при запуске внутри w3wp.exe. Настоящее исключение уже находится в журнале приложений Windows под источником IIS AspNetCore Module V2. Сначала прочитайте его, а затем расставьте приоритеты: отсутствующий общий фреймворк, несоответствие разрядности пула приложений, отсутствующая конфигурация или права пула.
500.30 не является причиной, это сообщение IIS о том, что ASP.NET Core Module запустил CLR внутри w3wp.exe, а приложение выбросило исключение до того, как успело начать слушать. Настоящее исключение почти наверняка уже есть на сервере: откройте Просмотр событий, перейдите в Журналы Windows > Приложение и найдите последнюю запись с источником IIS AspNetCore Module V2. Когда stdoutLogEnabled равно false, модуль перехватывает ошибки запуска и записывает до 30 КБ из них в это событие, включая трассировку стека. Если запись даёт только exception code = '0xe0434352' и больше ничего, установите stdoutLogEnabled="true" в web.config и обратитесь к сайту снова. Всё остальное - это расстановка приоритетов среди четырёх причин, которые действительно это вызывают.
HTTP Error 500.30 - ASP.NET Core app failed to start
Более старые сборки ASP.NET Core Module отображают ровно тот же сбой как HTTP Error 500.30 - ANCM In-Process Start Failure, и именно эта строка до сих пор используется в таблицах ошибок документации Microsoft. Обе означают одно и то же. Всё изложенное ниже проверено на .NET 11 (Preview 6, SDK 11.0.100-preview.6.26359.118) с ANCM V2 из текущего .NET Hosting Bundle. Механизм не менялся с тех пор, как in-process размещение стало значением по умолчанию в ASP.NET Core 3.0, поэтому каждый шаг без изменений применим к развёртываниям net8.0, net9.0 и net10.0.
Почему 500.30 - это симптом, а не диагноз
Начиная с ASP.NET Core 3.0 приложения по умолчанию используют модель размещения in-process. Свойство MSBuild <AspNetCoreHostingModel> по умолчанию равно InProcess, и dotnet publish записывает hostingModel="inprocess" в web.config. В этой модели нет отдельного процесса dotnet.exe. aspnetcorev2.dll загружает обработчик запросов in-process в рабочий процесс IIS, запускает там CoreCLR, и ваш Program.cs выполняется внутри w3wp.exe с использованием IISHttpServer вместо Kestrel.
Это даёт один процесс вместо двух и заметный выигрыш в пропускной способности, но полностью разрушает отчётность об ошибках. Когда приложение выбрасывает исключение до того, как app.Run() достигнет состояния прослушивания, у модуля внутри собственного процесса остаётся мёртвая CLR и один байт информации для браузера: запуск не удался. Отсюда единый код состояния, покрывающий отсутствующую строку подключения, 32-разрядный двоичный файл в 64-разрядном рабочем процессе, неустановленную среду выполнения и DirectoryNotFoundException на связке ключей защиты данных.
Два следствия стоит усвоить до того, как что-то менять:
startupTimeLimitне перезапускает приложение. При размещении in-process, если истекает стандартное окно запуска в 120 секунд, процесс завершается и не перезапускается, аrapidFailsPerMinuteне применяется. Размещение out-of-process повторяет попытку на следующем запросе. In-process - нет.- Пул приложений нельзя разделять. Размещение in-process требует отдельного пула на каждое приложение. Два in-process приложения в одном пуле дают
500.35, а смешивание in-process и out-of-process в одном пуле даёт500.34.
Минимальное воспроизведение
Наименьшее развёртывание, воспроизводящее ошибку, - это приложение, которое читает конфигурацию, существующую локально и отсутствующую на сервере:
// .NET 11 preview 6, C# 14. Program.cs
var builder = WebApplication.CreateBuilder(args);
string cs = builder.Configuration.GetConnectionString("Default")
?? throw new InvalidOperationException("Connection string 'Default' is missing.");
builder.Services.AddDbContext<AppDbContext>(o => o.UseSqlServer(cs));
var app = builder.Build();
app.MapGet("/", () => "ok");
app.Run();
Локально это работает, потому что appsettings.Development.json содержит нужную секцию, а ASPNETCORE_ENVIRONMENT равно Development. На сервере окружение - Production, appsettings.Production.json никогда не попадал в результат публикации, и исключение возникает на строке 3. F5 работает, развёртывание отдаёт 500.30, и в приложении при этом ничего не сломано.
Эта форма покрывает значительную долю реальных сообщений о 500.30: сбой связан с окружением, а значит, по построению невидим на машине разработчика.
Чтение журнала приложений, которое обычно завершает расследование
Сделайте это до того, как трогать web.config. На сервере запустите Просмотр событий от имени администратора и откройте Журналы Windows > Приложение, либо выполните запрос напрямую:
# Windows Server 2022+, PowerShell 5.1 or 7.x. Run elevated on the web server.
Get-WinEvent -FilterHashtable @{
LogName = 'Application'
ProviderName = 'IIS AspNetCore Module V2'
} -MaxEvents 5 | Format-List TimeCreated, Id, LevelDisplayName, Message
Вы ищете одну из трёх форм.
Форма 1, полезная. Полная управляемая трассировка стека. Модуль перехватил ваше необработанное исключение запуска и записал его в журнал событий, потому что stdoutLogEnabled равно false. Прочитайте тип исключения и верхний кадр, исправьте это, и вы закончили. Именно этот случай люди пропускают, потому что страница в браузере ничего им не сказала, и они решили, что сервер тоже не скажет.
Форма 2, непрозрачная:
Application '/LM/W3SVC/5/ROOT' with physical root 'C:\inetpub\wwwroot\myapp\'
hit unexpected managed exception, exception code = '0xe0434352'.
Please check the stderr logs for more information.
Application '/LM/W3SVC/5/ROOT' with physical root 'C:\inetpub\wwwroot\myapp\'
failed to load clr and managed application. CLR worker thread exited prematurely
0xe0434352 - это универсальный код Win32 для “управляемое исключение вышло наружу”, и не более того. Он не несёт ни типа, ни сообщения. Это документированная сигнатура x86-приложения в пуле, где не включены 32-разрядные приложения, но она также появляется всякий раз, когда исключение вышло там, где модуль не смог зафиксировать подробности. Переходите к журналу stdout.
Форма 3, вообще ничего. Ни одного события ANCM в течение минуты после вашего запроса. Обычно это значит, что модуль так и не добрался до запуска CLR, и на деле вы имеете дело с 500.0, 500.31 или 500.32, а не с исключением запуска. Смотрите раздел о вариантах в конце.
Включение журнала stdout
Правьте развёрнутый web.config на сервере, а не тот, что в проекте. Он перегенерируется при каждой публикации, что для временного диагностического переключателя как раз то, что нужно.
<?xml version="1.0" encoding="utf-8"?>
<!-- Deployed web.config, ASP.NET Core Module V2, .NET 11 -->
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="true"
stdoutLogFile=".\logs\stdout"
hostingModel="inprocess" />
</system.webServer>
</location>
</configuration>
Сохранение web.config перезапускает пул приложений, так что просто обратитесь к сайту снова. Папку logs для stdoutLogFile модуль создаёт сам и записывает файл с меткой времени и идентификатором процесса в имени, например stdout_20260805184032_5412.log. Учётной записи пула приложений нужен доступ на запись в эту папку:
icacls "C:\inetpub\wwwroot\myapp\logs" /grant "IIS AppPool\MyAppPool":(OI)(CI)M
Три замечания по чтению, экономящие время:
- Файл существует, но пуст. Процесс умер до того, как что-либо записал в stdout. Это указывает на несоответствие архитектуры или сбой загрузки нативной библиотеки, а не на ваш код.
- В файле есть обычные строки запуска, а затем всё обрывается. То, что выполняется сразу после последней строки, и есть подозреваемый.
- Выключите обратно.
stdoutLogEnabled="true"бесконечно создаёт новый файл при каждом перезапуске процесса, и документация прямо говорит, что оставленное включённым журналирование может положить приложение или сервер. Вернитеfalse, когда получите ответ.
Если stdout по-прежнему молчит, сбой находится ниже управляемого кода. Добавьте собственный отладочный журнал модуля:
<!-- ASP.NET Core Module V2 diagnostic logging. Remove after troubleshooting. -->
<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout"
hostingModel="inprocess">
<handlerSettings>
<handlerSetting name="debugFile" value=".\logs\aspnetcore-debug.log" />
<handlerSetting name="debugLevel" value="FILE,TRACE" />
</handlerSettings>
</aspNetCore>
В отличие от stdoutLogFile, для debugFile модуль папки не создаёт. Каталог logs должен уже существовать и быть доступен на запись учётной записи пула, иначе вы не получите ничего и сделаете неверный вывод. Этот журнал показывает разрешение hostfxr, какие версии фреймворка рассматривались и какая DLL не загрузилась.
Исправление 1: приложение выбросило исключение при запуске, и это большинство случаев
Если журнал событий или журнал stdout дал вам трассировку стека, это ваш случай. Группировка на практике:
- Конфигурация, присутствующая локально и отсутствующая на сервере.
appsettings.Production.jsonне попал в результат публикации, значение из User Secrets, у которого никогда не было продуктового аналога, переменная окружения, заданная только на вашей машине. Это сбой из-за отсутствующей строки подключения в его развёрточной форме. - Сбои графа внедрения зависимостей в
builder.Build(). ASP.NET Core проверяет области и граф сервисов при построении в Development, и любая проблема видаUnable to resolve service for typeили захваченной зависимости проявляется как 500.30 вместо полезной страницы. Смотрите unable to resolve service for type while attempting to activate и cannot consume scoped service from singleton. - Внешние зависимости, к которым обращаются при запуске. Key Vault с политикой доступа, не покрывающей управляемое удостоверение пула приложений, - это случай, который Microsoft прямо называет для 500.30. Миграция, выполняемая при старте, провайдер конфигурации, обращающийся к базе данных, загрузка документа обнаружения OIDC на сервере без исходящего доступа: всё это превращает сетевую проблему в сбой запуска.
- Доступ к сертификатам и защите данных. Загрузка сертификата X.509 из хранилища компьютера или сохранение связки ключей защиты данных по пути, недоступному учётной записи пула на запись, выбрасывает исключение до первого запроса.
Структурное решение для всей этой категории - сделать сбои запуска явными и читаемыми, а не случайными. Проверка конфигурации при старте через IValidateOptions<T> и ValidateOnStart превращает “приложение отдаёт 500.30” в именованное OptionsValidationException, перечисляющее ровно те настройки, которых не хватает, а это разница между пятиминутным исправлением и потерянным днём.
Чтобы увидеть исходное исключение в браузере на тестовом сервере, добавьте переменную окружения в web.config, и никогда не делайте этого на публичном сервере:
<!-- Staging and test servers only. Do not ship this to an internet-facing host. -->
<aspNetCore processPath="dotnet" arguments=".\MyApp.dll" hostingModel="inprocess">
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
<environmentVariable name="ASPNETCORE_DETAILEDERRORS" value="true" />
</environmentVariables>
</aspNetCore>
Исправление 2: общий фреймворк, на который нацелено приложение, не установлен
Microsoft ставит эту причину первой среди причин 500.30: приложение нацелено на версию общего фреймворка ASP.NET Core, которой нет на машине. Проверьте, что на сервере есть на самом деле:
dotnet --list-runtimes
Вам нужна строка Microsoft.AspNetCore.App, чья мажорная версия совпадает с вашим TargetFramework, и нужна она в той же разрядности, что и пул приложений. Если приложение - net11.0, а на сервере максимум Microsoft.AspNetCore.App 10.0.x, это и есть ответ, потому что ASP.NET Core по умолчанию не выполняет roll forward между мажорными версиями.
Установите .NET Hosting Bundle, который ставит среду выполнения, общий фреймворк ASP.NET Core и ANCM одним пакетом. Два правила установки вызывают больше случаев 500.30, чем сама загрузка:
- IIS должен быть установлен до Hosting Bundle. Если бандл поставили первым, повторный запуск установщика для восстановления обязателен, а не опционален.
- Перезапустите веб-сервер после установки. Установщик меняет системный
PATH, и ASP.NET Core также не выполняет roll forward для патч-версий пакетов общего фреймворка, поэтому тот же перезапуск нужен после каждого обновления бандла:
net stop was /y
net start w3svc
Полный iisreset тоже подойдёт. Пропуск этого шага и есть причина того, что “я установил среду выполнения, а оно всё равно падает” - настолько частое продолжение разговора.
Исправление 3: приложение и пул приложений расходятся в разрядности
Размещение in-process требует, чтобы архитектура приложения и установленной среды выполнения совпадала с архитектурой пула приложений. Слоя адаптации нет. 32-разрядный двоичный файл не может запустить CoreCLR внутри 64-разрядного w3wp.exe.
В диспетчере IIS выберите пул приложений, откройте Дополнительные параметры и задайте Разрешены 32-разрядные приложения:
Trueдля x86-приложения, включая автономное x86-развёртывание, опубликованное 32-разрядным SDK.Falseдля x64-приложения.
Или из командной строки:
%windir%\system32\inetsrv\appcmd set apppool /apppool.name:MyAppPool /enable32BitAppOnWin64:false
Пока вы там, задайте Версию среды CLR .NET равной Без управляемого кода в основных параметрах. ASP.NET Core запускает CoreCLR самостоятельно и никогда не нуждается в настольной CLR внутри рабочего процесса. Это документировано как необязательное, но рекомендуемое, и убирает целый класс запутанных взаимодействий с устаревшими модулями.
Отдельная ловушка Hosting Bundle: если вы установили его с OPT_NO_X86=1, на этой машине вообще нет 32-разрядной среды выполнения, и x86-приложение упадёт независимо от настроек пула.
Исправление 4: учётная запись пула не может прочитать то, что ей нужно
Стандартная ApplicationPoolIdentity - это виртуальная учётная запись, и любой 500.30, вызванный правами доступа, выглядит точно так же, как любой другой 500.30. Если удостоверение было изменено с ApplicationPoolIdentity на доменную или служебную учётную запись, проверьте, что у неё есть доступ на чтение к папке развёртывания и на запись всюду, куда приложение пишет. Выдайте права на папку по имени пула:
icacls "C:\inetpub\wwwroot\myapp" /grant "IIS AppPool\MyAppPool":(OI)(CI)RX
Два случая стоит проверить напрямую: чтение закрытого ключа сертификата из хранилища компьютера требует ACL на контейнер ключей, а любой код, обращающийся к %USERPROFILE%, требует Загрузить профиль пользователя равным True у пула приложений. По умолчанию там True, и в защищённых окружениях его часто отключают.
Сократите область поиска вдвое, запустив приложение вне IIS
Прежде чем тратить ещё час на конфигурацию IIS, зайдите на сервер, откройте консоль в папке развёртывания и запустите приложение напрямую:
cd C:\inetpub\wwwroot\myapp
set ASPNETCORE_ENVIRONMENT=Production
dotnet MyApp.dll
Исключение печатается в консоль с полной трассировкой стека и без какой-либо настройки журналирования. Если оно возникает здесь, проблема в вашем приложении или его конфигурации, а IIS ни при чём, и это ведёт вас прямо к Исправлению 1. Если приложение стартует чисто и обслуживает http://localhost:5000, проблема в слое размещения: разрядность, права или модуль, и это ведёт к Исправлению 2, 3 или 4. Одна эта команда решает, какая половина статьи вам нужна.
Обратите внимание на переменную окружения. Запуск под вашей собственной учётной записью с вашим окружением - это не то же самое, что запуск под учётной записью пула, поэтому чистый прогон здесь не доказывает, что права на файлы верны. Он доказывает, что верны код и развёрнутые файлы конфигурации.
Соседние коды, которые не являются 500.30
Поисковый трафик по 500.30 собирает множество похожих случаев. Если на вашей странице написано что-то другое, это другая проблема с другим решением:
500.0 - ANCM In-Process Handler Load Failure: модуль вообще не смог загрузить обработчик запросов in-process. НеверныйprocessPath, не установлен Hosting Bundle, IIS не перезапущен после установки, либо отсутствует распространяемый пакет VC++.500.31 - ANCM Failed to Find Native Dependencies: не установленMicrosoft.NETCore.AppилиMicrosoft.AspNetCore.App. Журнал событий называет точный фреймворк и версию, которых не нашлось. Установите, смените целевую версию либо публикуйте автономно.500.32 - ANCM Failed to Load dll: несоответствие архитектуры процессора, та же первопричина, что и в Исправлении 3, но проявившаяся уровнем ниже.500.33 - ANCM Request Handler Load Failure: приложение не ссылается на фреймворкMicrosoft.AspNetCore.App. Проверьте.runtimeconfig.json. Консольное приложение сMicrosoft.NET.SdkвместоMicrosoft.NET.Sdk.Webдаёт именно это.500.34и500.35: смешанные модели размещения либо два in-process приложения в одном пуле. Разнесите их по разным пулам.500.36 - ANCM Out-Of-Process Handler Load Failure: рядом сaspnetcorev2.dllотсутствуетaspnetcorev2_outofprocess.dll. Восстановите Hosting Bundle.500.37 - ANCM Failed to Start Within Startup Time Limit: запуск превысил 120 секунд. УвеличьтеstartupTimeLimitлибо разнесите по времени старт множества приложений, конкурирующих за процессор на одной машине.500.38 - ANCM Application DLL Not Found: вы опубликовали приложение одним файлом, а размещение in-process этого не поддерживает. Задайте<PublishSingleFile>false</PublishSingleFile>либо перейдите на<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>.502.5 - Process Failure: только для размещения out-of-process. Фоновый процесс не запустился либо не начал слушать%ASPNETCORE_PORT%. Часто этоBadImageFormatExceptionиз-за несоответствия RID, видимая в журнале stdout.500.19: ошибка конфигурации IIS при чтении самогоweb.config, обычно из-за того, что ANCM не зарегистрирован или конфигурация некорректна. Приложение вообще не вступало в игру.
Переход на размещение out-of-process - это законный диагностический приём, а не решение. Установка hostingModel="outofprocess" в web.config перезапускает рабочий процесс и выполняет ваше приложение как дочерний dotnet.exe, где сбои запуска наблюдать намного проще, а requestTimeout и rapidFailsPerMinute снова применяются. Используйте это, чтобы получить читаемую ошибку, а затем вернитесь к in-process ради производительности.
Общая форма расследования 500.30 коротка, если идти по порядку: журнал событий, затем запуск из консоли, затем разрядность и среда выполнения. Долгим днём это становится только тогда, когда вы начинаете со страницы в браузере и пытаетесь угадать.
Похожие материалы
- Fix: Unable to resolve service for type X while attempting to activate Y - самое частое управляемое исключение, скрывающееся за 500.30.
- Fix: Cannot consume scoped service from singleton охватывает другой сбой внедрения зависимостей, который проявляется только после построения контейнера.
- Как проверять параметры при запуске с помощью IValidateOptions<T> в .NET 11 превращает “приложение не запустилось” в именованное исключение, которое говорит, какая настройка неверна.
- Fix: No connection string named ‘DefaultConnection’ could be found - классический пробел в конфигурации, доживающий до самого развёртывания.
- Fix: Could not load file or assembly в опубликованном приложении разбирает проблемы результата публикации, которые проявляются как сбой запуска.
- Миграция с .NET 8 на .NET 11: полный чеклист включает шаг обновления Hosting Bundle, которого требует смена мажорной версии на каждом сервере IIS.
Источники
- Troubleshoot ASP.NET Core on Azure App Service and IIS на MS Learn, для определений с 500.30 по 500.38, журнала stdout и отладочного журнала ANCM.
- Common error troubleshooting for Azure App Service and IIS with ASP.NET Core для дословных строк журнала приложений, включая сигнатуру
0xe0434352. - ASP.NET Core Module (ANCM) for IIS для атрибутов элемента
aspNetCore, их значений по умолчанию и особенностей размещения in-process. - Host ASP.NET Core on Windows with IIS для порядка установки Hosting Bundle,
net stop was /yи настройки пула приложений. - Install the .NET Hosting Bundle для параметров установщика, включая
OPT_NO_X86.
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.