Chuyện Gì Đã Xảy Ra
Bạn publish ứng dụng ASP.NET Core, đưa lên IIS, truy cập URL và gặp ngay lỗi này:
HTTP Error 500.30 - ASP.NET Core app failed to start
ANCM In-Process Start Failure
IIS vẫn tải trang được — tức là bản thân web server vẫn đang hoạt động. Nhưng ANCM (ASP.NET Core Module, cầu nối giữa IIS và .NET runtime) không thể chuyển tiếp sang tiến trình .NET. Trình duyệt hầu như không cho bạn thông tin gì để xử lý. Lỗi thực sự đang ẩn ở một chỗ khác.
Lỗi này thường xuất hiện ngay sau lần deploy đầu tiên, sau khi nâng cấp phiên bản .NET, hoặc khi chuyển sang một server chưa từng chạy ứng dụng .NET bao giờ.
Bước 1: Bật stdout Logging để Xem Lỗi Thực Sự
Trang 500.30 chỉ là lớp bọc bên ngoài. Exception thực sự xảy ra lúc khởi động nằm trong stdout log — nhưng mặc định tính năng này bị tắt. Mở file web.config đã publish và chỉnh như sau:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<aspNetCore processPath="dotnet"
arguments=".\YourApp.dll"
stdoutLogEnabled="true"
stdoutLogFile=".\logs\stdout"
hostingModel="inprocess" />
</system.webServer>
</configuration>
Đặt stdoutLogEnabled="true" và tự tạo thư mục logs — IIS sẽ không tự tạo cho bạn:
mkdir C:\inetpub\wwwroot\YourApp\logs
Restart lại app pool, truy cập URL, rồi mở file log. Nếu thiếu runtime sẽ hiện như thế này:
The framework 'Microsoft.NETCore.App', version '8.0.0' was not found.
Lỗi cấu hình lúc khởi động trông sẽ kiểu như:
Unhandled exception. System.InvalidOperationException: Unable to resolve service for type 'IDbContext'
Ngoài ra hãy kiểm tra Windows Event Viewer → Windows Logs → Application. Lọc theo source IIS AspNetCore Module V2 — các exception lúc khởi động thường xuất hiện ở đó trước cả khi file log được ghi.
Bước 2: Kiểm Tra Các Nguyên Nhân Phổ Biến Nhất
Nguyên nhân 1 — Thiếu hoặc Sai phiên bản .NET Hosting Bundle
Đây là nguyên nhân số 1 trên các server mới. Hosting Bundle cài đặt ASP.NET Core runtime và IIS module cùng nhau. Nếu không có nó, ANCM không có gì để tải ứng dụng của bạn vào.
Kiểm tra những gì đã được cài:
dotnet --list-runtimes
Môi trường .NET 8 được cài đúng sẽ hiển thị cả hai dòng này:
Microsoft.AspNetCore.App 8.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 8.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Không có gì, hoặc sai phiên bản? Tải và cài đặt Hosting Bundle đúng phiên bản từ Microsoft. Phiên bản phải khớp với target framework của ứng dụng — net8.0 cần .NET 8 Hosting Bundle, không phải .NET 7.
Sau khi cài xong, chạy:
iisreset
Đừng bỏ qua bước reset — IIS cache lại cấu hình module và sẽ không nhận bundle mới nếu không restart.
Nguyên nhân 2 — App Pool Chưa Được Đặt Thành "No Managed Code"
ASP.NET Core tự quản lý CLR của nó. Nếu IIS cũng cố load một CLR, sẽ xảy ra xung đột. Mở IIS Manager → Application Pools → pool của bạn → Basic Settings, rồi đặt .NET CLR Version thành No Managed Code.
# Hoặc dùng PowerShell:
Import-Module WebAdministration
Set-ItemProperty IIS:\AppPools\YourAppPool managedRuntimeVersion ""
Nguyên nhân 3 — Ứng Dụng Crash Trong Quá Trình Khởi Động (Lỗi Configuration hoặc DI)
Các exception xảy ra trong Program.cs — connection string sai, thiếu config key, lỗi đăng ký DI — đều hiển thị ra ngoài là 500.30. ANCM không biết chuyện gì đã xảy ra; nó chỉ thấy tiến trình chết. Stdout log hoặc Event Viewer sẽ có exception thực sự.
Cách nhanh để xác nhận: chạy trực tiếp ứng dụng từ thư mục đã publish:
cd C:\inetpub\wwwroot\YourApp
dotnet YourApp.dll
Console bị crash? Đó chính là lỗi khởi động của bạn. Sửa ở đó rồi publish lại.
Nguyên nhân 4 — Chưa Đặt ASPNETCORE_ENVIRONMENT
Các ứng dụng lấy config theo môi trường — secrets, connection strings, feature flags — sẽ crash lúc khởi động nếu ASPNETCORE_ENVIRONMENT chưa được đặt. IIS không tự cấu hình điều này. Đặt biến trực tiếp trên app pool:
# PowerShell — đặt ở cấp độ app pool:
$envVars = (Get-WebConfiguration system.applicationHost/applicationPools/add[@name='YourAppPool']/environmentVariables)
$envVars.Add(@{name='ASPNETCORE_ENVIRONMENT'; value='Production'})
Set-WebConfiguration system.applicationHost/applicationPools/add[@name='YourAppPool']/environmentVariables -Value $envVars
Hoặc đặt trong web.config bên trong thẻ <aspNetCore>:
<aspNetCore ...>
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" />
</environmentVariables>
</aspNetCore>
Nguyên nhân 5 — Không Khớp Giữa 32-bit và 64-bit
Lỗi không khớp bitness rất dễ bỏ qua. Nếu app pool đang bật Enable 32-bit Applications thành True trong khi bạn deploy bản build 64-bit, ANCM sẽ không load được — hoặc ngược lại. Kiểm tra Advanced Settings của pool và đảm bảo khớp với publish target.
Đối với ứng dụng .NET 6 trở lên, hãy publish 64-bit và tắt 32-bit trên pool.
Nguyên nhân 6 — Quyền Truy Cập File
Identity của app pool cần quyền đọc/thực thi trên thư mục ứng dụng và quyền ghi trên thư mục logs. Identity mặc định là IIS AppPool\YourAppPool. Lỗi phân quyền sẽ xuất hiện trong Event Viewer với thông báo "Access is denied" — nhưng ANCM vẫn báo 500.30 trong mọi trường hợp.
icacls "C:\inetpub\wwwroot\YourApp" /grant "IIS AppPool\YourAppPool:(OI)(CI)RX"
icacls "C:\inetpub\wwwroot\YourApp\logs" /grant "IIS AppPool\YourAppPool:(OI)(CI)M"
Bước 3: Xác Nhận Đã Sửa Xong
Đã sửa được gì đó? Đây là cách xác nhận nó thực sự hoạt động:
- Recycle app pool:
Restart-WebAppPool -Name YourAppPool - Truy cập vào site — bạn sẽ thấy ứng dụng của mình thay vì trang 500.30
- Kiểm tra stdout log để tìm dòng khởi động Kestrel:
Now listening on: http://localhost:PORT - Thử một API endpoint hoặc trang cụ thể để xác nhận ứng dụng đang xử lý request
Khi đã hoạt động, hãy tắt stdout logging: đặt stdoutLogEnabled="false" trong web.config. Nếu để bật trên môi trường production, nó sẽ ghi một dòng log mỗi request và có thể làm đầy ổ cứng chỉ trong vài giờ khi có lượng truy cập thực tế.
Bài Học Rút Ra
500.30 luôn là lỗi xảy ra trong quá trình khởi động — ứng dụng chưa kịp xử lý một request nào cả. Chín mươi phần trăm trường hợp quy về hai điều: thiếu Hosting Bundle trên server mới, hoặc exception lúc khởi động mà lẽ ra đã thấy ngay nếu bạn chạy dotnet YourApp.dll trực tiếp từ đầu.
Hãy bật stdout logging trước lần deploy đầu tiên, đừng đợi đến khi có sự cố. Và hãy test dotnet YourApp.dll từ thư mục publish trước khi trỏ IIS vào đó — bước đơn giản đó giúp rút ngắn vòng phản hồi từ vài phút xuống còn vài giây.

