The ASP.NET Core Hosting Bundle installs three things: the .NET runtime, the ASP.NET Core shared framework, and the AspNetCoreModuleV2 IIS module that reverse-proxies requests to the out-of-process Kestrel server. Most “it works locally but 500s on IIS” failures trace to one of those three being absent or mismatched.
Install Order and Verification
Install the bundle after IIS, or the module registration step has no IIS to register with. If you install IIS later, repair the bundle. Then verify all three components independently rather than assuming:
# 1. runtimes present
dotnet --list-runtimes
# 2. module registered globally
Get-WebGlobalModule | Where-Object { $_.Name -like 'AspNetCoreModule*' } |
Select-Object Name, Image
# 3. handler present in the site's config
Get-WebConfiguration -PSPath "IIS:\Sites\MyApp" -Filter "system.webServer/handlers/*" |
Where-Object { $_.Name -like 'aspNetCore' }
dotnet --list-runtimes must show a Microsoft.AspNetCore.App entry matching your target framework. A machine with only Microsoft.NETCore.App will fail every Core web app. If the module is missing, restart the Windows Process Activation Service — not just W3SVC:
net stop was /y
net start w3svc
Application Pool and web.config
Three settings in the app pool and one in web.config account for the bulk of configuration errors.
| Setting | Required value | Consequence if wrong |
|---|---|---|
| App pool .NET CLR version | No Managed Code | CLR load failures, module never loads |
| Pipeline mode | Integrated | Handler mapping failures |
| Identity | ApplicationPoolIdentity (default) or a low-privilege service account | File/registry access errors, or excessive rights |
| 32-bit applications | false unless you have 32-bit native deps | Mixed-bitness load failures |
web.config hostingModel | inprocess for performance, outofprocess for isolation | 500.30 with inprocess/outofprocess mismatch |
Import-Module WebAdministration
Set-ItemProperty "IIS:\AppPools\MyApp" managedRuntimeVersion ""
Set-ItemProperty "IIS:\AppPools\MyApp" managedPipelineMode Integrated
Set-ItemProperty "IIS:\AppPools\MyApp" enable32BitAppOnWin64 $false
Reading 500.19 vs 500.30 Correctly
These are unrelated faults that get conflated constantly.
- 500.19 — Configuration Error. IIS could not parse
web.config. The response names the offending section. Root causes: a<system.webServer>section locked at server level, a duplicate section handler, or a 32/64-bit module mismatch. The fix is almost always a missingappcmd unlock configor a duplicate module registration, not anything in your application. - 500.30 — In-Process Start Failure. The module loaded but the app failed to start. This is your application, not IIS. Read the actual exception from the Windows Application log or by running the app standalone against the same DLL — that is the fastest path to the real error.
# surface the real .NET exception rather than the generic 500 page
Get-WinEvent -LogName Application -MaxEvents 20 |
Where-Object ProviderName -like '*IIS AspNetCore Module*' |
Select-Object TimeCreated, Message | Format-List
Enable stdout logging in web.config for an app that starts fine standalone but dies under IIS — it captures the startup exception to a file you can read without a debugger:
<aspNetCore processPath="dotnet" arguments=".\MyApp.dll"
stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
Create the logs folder yourself — the module will not create it, and a missing folder silently produces no log. Turn stdout logging off once resolved; it prevents log rotation and grows without bound.
Deployment and Permissions
The app pool identity needs read and execute on the deployment folder and write access only where the application genuinely writes. Applying this correctly is the difference between a working deployment and a stream of 500.30s on first request:
$acl = "IIS AppPool\MyApp"
icacls "C:\inetpub\MyApp" /grant "${acl}:(OI)(CI)(RX)" /T /C
icacls "C:\inetpub\MyApp\logs" /grant "${acl}:(OI)(CI)(M)" /T /C
icacls "C:\inetpub\MyApp\App_Data" /grant "${acl}:(OI)(CI)(M)" /T /C
Publish with Web Deploy or dotnet publish, then confirm the deployed target framework matches the installed runtime — a version skew between the build SDK and the server runtime produces startup failures that look like configuration problems. The deployment pipeline mechanics are covered in our ASP.NET Core on IIS hosting guide.
Checklist Before You Go Live
dotnet --list-runtimesshows the matchingMicrosoft.AspNetCore.Appversion.AspNetCoreModuleV2appears inGet-WebGlobalModule.- App pool set to No Managed Code, Integrated pipeline, 32-bit disabled.
- App pool identity has RX on the site root, M on write paths only.
stdoutLogEnabledisfalsein production.- HTTPS binding and HSTS configured — steps in our IIS HTTPS bindings and certificates walkthrough.
- Application Pool recycling configured so a recycle does not drop in-flight requests.
Hosting It
A single low-traffic ASP.NET Core site is comfortable on 2 vCPU / 4 GB with NVMe storage. InterServer Windows VPS supplies licensed Windows Server with full IIS control and no restricted handlers, and code TRYINTERSERVER brings the first month to $0.01. If your build pipeline needs hourly provisioning or you want the option of a licensed Windows image on fast NVMe, Vultr and Database Mart are worth comparing.
Tuning the Reverse Proxy to Kestrel
The module forwards requests to Kestrel over a local loopback port. Two defaults are worth changing on a busy site: the request body limit, because IIS caps uploads at 30 MB regardless of your application’s own limits, and the shutdown timeout, because a graceful recycle needs long enough for in-flight requests to drain.
# raise the IIS-level upload cap to 100 MB
Set-WebConfigurationProperty -PSPath "IIS:\Sites\MyApp" `
-Filter "system.webServer/security/requestFiltering/requestLimits" `
-Name maxAllowedContentLength -Value 104857600
# confirm the app pool shutdown timeout gives in-flight requests time to finish
Get-ItemProperty "IIS:\AppPools\MyApp" |
Select-Object shutdownTimeLimit, startMode
A mismatch between the IIS limit and your Kestrel MaxRequestBodySize produces confusing 413 responses — the request is rejected before your middleware ever sees it. Set the IIS limit slightly higher than the application limit and let the application produce the meaningful error. Recycling behaviour and overlap settings are covered in our IIS application pool recycling guide, which matters more for in-process hosting where a recycle restarts your managed code.



