ASP.NET Core App Won't Start on IIS: The 7 Most Common Causes

You published your ASP.NET Core app to IIS, opened the browser, and got an error page. Not the developer-friendly Kestrel exception page — some flavor of generic IIS error: 500.30, 502.5, 503, "HTTP Error 500.19," or worst of all, no response at all. The app worked perfectly locally; the build succeeded; the publish output looked complete. So why won't it start?

This is the structured guide to the seven causes that account for almost every "ASP.NET Core app won't start on IIS" incident, ranked by frequency. Each has a distinct error signature you can read off the IIS response, and each has a specific fix.

Read the error number first — it narrows the cause

What IIS showsWhat it meansSection

HTTP Error 503 — Service UnavailableApp pool is stopped or crashed before servingCause 1

HTTP Error 500.19 — Internal Server Error (config)web.config syntax error or missing handlerCause 2

HTTP Error 500.30 — ASP.NET Core app failed to startIn-process worker crashed during Program.MainCause 3

HTTP Error 502.5 — ANCM Out-of-Process Startup FailureOut-of-process worker (Kestrel) crashed during launchCause 3

HTTP Error 500 — genericApp started but threw on the first requestCause 6

Connection refused / no responseApp pool not bound, DNS wrong, or firewall blockCause 7

Take 30 seconds to identify the exact error message and code before diving in. The error number cuts the problem space from "could be anything" to "two or three specific causes."

The 7 causes, in order of frequency

  • The application pool is stopped (HTTP 503)

IIS shows HTTP Error 503 — Service Unavailable. The app pool either failed to start, crashed and disabled itself after rapid failures, or was manually stopped.

Diagnostic: Open IIS Manager → Application Pools. Find your app's pool. The "Status" column should show "Started". If it shows "Stopped," there's your problem.

Why pools auto-stop: IIS has a "rapid fail protection" feature. If the worker process crashes 5 times within 5 minutes, IIS disables the pool to prevent thrashing. Until you fix the underlying crash, every Start of the pool will fail again within seconds.

Fix: Start the app pool. If it stops again immediately, the worker is crashing during startup — check Event Viewer (Windows Logs → Application) for the .NET runtime exception that's killing it. The Event Viewer entry has the full stack trace IIS won't show in the browser.

On Adaptive Web Hosting plans, you control your dedicated app pool through Plesk for Windows — "Recycle" / "Stop" / "Start" buttons in the domain settings, plus access to recent Event Viewer entries in the IIS log viewer.

  • web.config syntax error (HTTP 500.19)

The error code 500.19 specifically means IIS couldn't parse your web.config file. Either the XML is malformed, a configuration section is locked at a higher level, or the ASP.NET Core Module handler isn't registered.

Diagnostic: The 500.19 error page in IIS gives you a "Detailed Error Information" block with the exact line number and the conflicting element. Read it carefully — IIS is precise about which line broke. Common culprits:

A handler element duplicated from a parent web.config — need <remove name="..."/> before <add ...>

A custom rewrite rule with bad syntax

An environment variable element with a missing closing tag

Fix: If you didn't write the web.config by hand, regenerate it via dotnet publish — the auto-generated version is correct. If you did customize it, the IIS error message tells you the offending line; revert to the auto-generated version and re-apply your customization one piece at a time.

  • The .NET runtime isn't installed (HTTP 500.30 / 502.5)

HTTP 500.30 ("ASP.NET Core app failed to start") or 502.5 ("ANCM Out-of-Process Startup Failure") almost always means the runtime your app targets isn't available on the host. Your .csproj says net10.0; the server only has net8.0. The worker process can't load your DLL and the ASP.NET Core Module synthesizes the 500.30 / 502.5 error.

Diagnostic: On the server, run dotnet --list-runtimes. Look for Microsoft.AspNetCore.App 10.x.x (or whatever version your project targets). You need the ASP.NET Core Runtime, not just the .NET Runtime — the latter lacks the ASP.NET-specific assemblies.

Fix: Install the matching ASP.NET Core Runtime from Microsoft's downloads page. Or, more cleanly, change your .csproj TargetFramework to a runtime that IS installed.

On Adaptive Web Hosting plans, every supported LTS runtime is pre-installed — .NET 8 LTS and .NET 10 LTS side-by-side, so 500.30 / 502.5 from missing runtime is rare here. For the broader diagnostic flow for 500.30 specifically, see our HTTP 500.30 troubleshooting guide.

  • The "Managed Code" setting is wrong

This is the most common stumble for developers new to hosting ASP.NET Core on IIS. The IIS application pool has a "Managed Code Version" dropdown with three options:

.NET CLR Version v4.0 — classic ASP.NET / .NET Framework 4.x apps

.NET CLR Version v2.0 — legacy ASP.NET 2.0/3.5 apps

No Managed Code — ASP.NET Core / .NET 5+ apps

If you set it to v4.0 (the default), IIS tries to load the classic ASP.NET pipeline before your app gets a request. The result varies — sometimes 500.19, sometimes 500.30, sometimes a strange "the page is blank" hang.

Fix: Set the app pool to No Managed Code for ASP.NET Core. The setting is counterintuitive (your app very much uses managed code!) but it's the correct choice because .NET Core's hosting model runs out-of-process or in-process via the ASP.NET Core Module — not via the classic CLR. On Adaptive Web Hosting plans, the default for newly-created dedicated app pools is "No Managed Code," so this trip-wire only fires if you manually changed it.

  • File permissions on the published folder

The app pool identity (typically IIS APPPOOL\YourPoolName, or a domain account if you configured one) needs read access to every file your app loads. If you uploaded the publish output via FTP under a different account, the app pool may not have read access to the DLLs.

Diagnostic: From the server, run:

icacls "C:\inetpub\wwwroot\yoursite" /T

You should see entries for IIS_IUSRS with (OI)(CI)(RX) (Object Inherit, Container Inherit, Read+Execute). If those entries are missing or the app pool identity isn't covered, the worker can't read its own DLLs.

Fix: Grant read+execute to IIS_IUSRS on the site root recursively:

icacls "C:\inetpub\wwwroot\yoursite" /grant "IIS_IUSRS:(OI)(CI)RX" /T

On Adaptive Web Hosting plans, Plesk manages permissions automatically when you upload via its File Manager, FTP through your hosting account, or Web Deploy. If you suspect permission issues, re-uploading through Plesk's File Manager re-applies the correct ACLs.

  • The app started but threw on the first request (HTTP 500 generic)

This isn't strictly "won't start" — the app pool is running, the worker is alive, but the first incoming request immediately throws an exception and returns HTTP 500. Common causes:

Missing connection string in appsettings.Production.json — builder.Configuration.GetConnectionString() returns null

Strongly-typed configuration section (builder.Services.Configure<Foo>()) where Foo has a required property that's missing

An IHostedService that throws during StartAsync — technically blocks app pool startup but presents differently per .NET version

A middleware in the pipeline that throws on initialization

Diagnostic: Enable stdout logging temporarily in web.config:

<aspNetCore processPath="dotnet" arguments=".\YourApp.dll"

stdoutLogEnabled="true"

stdoutLogFile=".\logs\stdout"

hostingModel="inprocess" />

Restart the app pool, reproduce the error, then check .\logs\stdout_*.log for the actual exception with the stack trace. Turn stdout logging off when you're done — leaving it on fills the disk over time.

  • The site isn't bound to the right port or hostname

The connection refuses entirely — no response, browser shows "Site can't be reached." This is below the ASP.NET layer; IIS isn't seeing your request at all.

Common causes:

The IIS site binding doesn't include the hostname you're testing — check IIS Manager → Sites → your site → Bindings

DNS hasn't propagated yet (recently updated A record)

The hosting plan's firewall blocks the port (rare but possible)

The certificate-protected HTTPS binding is broken (the HTTP binding still works at port 80)

Fix: Verify the site binding matches the hostname you're requesting. Test by IP: http://[hosting-server-ip]/yoursite/ — if that works, the issue is DNS or the binding hostname.

The diagnostic flow we recommend

Read the exact error code in the browser. 503 vs 500.30 vs 502.5 vs 500.19 each point at distinct causes; use the table at the top to narrow it.

Check the app pool status in IIS Manager or Plesk for Windows. Stopped or "after 5 failures" disabled tells you to look at Event Viewer for the underlying crash.

Check Event Viewer (Application + System logs filtered to source = ".NET Runtime" or "WAS"). The full stack trace IIS won't show in the browser is here.

Enable stdout logging if the error is 500.30 / 502.5. Reproduce, then read logs/stdout_*.log for the .NET exception.

Run dotnet --list-runtimes on the server. If your target runtime isn't there, that's your issue.

Check the app pool's "Managed Code" setting — "No Managed Code" for ASP.NET Core, not v4.0.

Check file permissions with icacls. IIS_IUSRS needs read+execute on every file.

Check the site binding — hostname, port, certificate state.

This sequence resolves the vast majority of "won't start" incidents within 10-15 minutes.

Production-ready settings that prevent these failures

For an IIS-hosted ASP.NET Core app, the following configuration prevents most won't-start incidents:

In your application pool (via Plesk for Windows or IIS Manager)

Managed Code Version: No Managed Code

Pipeline Mode: Integrated

Identity: ApplicationPoolIdentity (default) unless you specifically need a domain account

Idle Time-out: 0 (don't suspend; cold-start is painful for users)

Regular Time Interval (recycle): 0 or ≥24 hours (avoid mid-day recycles)

Rapid-Fail Protection: enabled (default); your app shouldn't be crashing 5x in 5 minutes; if it is, fix the underlying crash rather than disabling protection

In your web.config

<aspNetCore processPath="dotnet" arguments=".\YourApp.dll"

stdoutLogEnabled="false"

stdoutLogFile=".\logs\stdout"

hostingModel="inprocess" />

Set stdoutLogEnabled="false" for normal operation; flip to true only when actively diagnosing. hostingModel="inprocess" for almost all production workloads.

In your appsettings.Production.json

Make sure every configuration key your Program.cs reads eagerly is present and non-null. Use the required modifier on options classes where appropriate so missing config fails fast and clearly rather than appearing as a generic 500 later.

How Adaptive Web Hosting's infrastructure helps

The most common platform-level causes of ASP.NET Core won't-start failures on shared Windows hosting are wrong default app pool settings (Managed Code = v4.0), missing runtime versions (host lags Microsoft's release cycle), and permission issues from inconsistent FTP / Web Deploy uploads.

Adaptive Web Hosting's ASP.NET Core plans address all three:

Dedicated app pools with correct ASP.NET Core defaults — "No Managed Code" pre-configured, recycle settings tuned for production

All current .NET LTS runtimes pre-installed — .NET 8 LTS and .NET 10 LTS side-by-side; we add new LTS versions on Microsoft's release timeline

Plesk for Windows manages permissions consistently — uploads via File Manager, FTP through your hosting account, or Web Deploy all leave the ACLs correct for the app pool identity

IIS log viewer + stdout log access through Plesk — no RDP needed to read the diagnostic output

Frequently asked questions

Why does my app work on Kestrel locally but fail on IIS?

Almost always one of three things: the production server lacks your target runtime (cause 3), your appsettings.Production.json references resources only your dev machine has (cause 6), or the app pool's Managed Code setting is wrong (cause 4). Enable stdout logging, reproduce, and read the actual exception — the local-vs-prod difference is in the log.

The error page says to enable stdout logging but I already did and the log file is empty.

The app pool identity needs write permission on the logs folder. If the folder doesn't exist or the identity can't write to it, no log gets generated. Create the folder manually and grant IIS_IUSRS "Modify" permission via icacls, restart the pool, and reproduce.

Does Adaptive Web Hosting have a way to see startup errors without RDP?

Yes — Plesk for Windows includes an IIS log viewer that shows recent request errors and a file manager that gives direct access to your site's logs/ folder. For "the app crashed at startup" issues, enabling stdoutLogEnabled="true" writes to that same logs folder where Plesk's file manager can read it. No RDP needed.

I changed the Managed Code setting and the app still won't start.

Recycle the app pool after changing the setting — the change only takes effect on the next worker startup. In IIS Manager: right-click the pool → Recycle. In Plesk: the pool's "Recycle" button.

How do I tell if it's a config problem or a runtime crash?

The error code distinguishes them. 500.19 = config / web.config problem. 500.30 / 502.5 = runtime crash. 503 = app pool issue. The browser shows the error code in the page heading and as the HTTP status — don't ignore it.

Should I use in-process or out-of-process hosting?

In-process for almost every production workload. It's faster (no Kestrel reverse-proxy hop), lower latency, and uses less memory. Out-of-process is only needed if you have specific scenarios that require Kestrel as an HTTP server (custom HTTP/2 middleware, specific socket-level control, or 32-bit native code requirements). The hostingModel="inprocess" attribute in web.config controls this.

What if my app starts on staging but not production?

Diff the two environments systematically: which runtime version is installed (dotnet --list-runtimes), what app pool settings differ, what's in appsettings.Staging.json vs appsettings.Production.json, and whether the database connection string in production points at a reachable server. The crash log (stdout or Event Viewer) tells you which difference matters.

Bottom line

"ASP.NET Core won't start on IIS" is almost always one of seven specific causes, and the error code IIS shows narrows the list to 1-2 before you even look at logs. App pool stopped (503), config error (500.19), runtime missing or in-process crash (500.30 / 502.5), wrong Managed Code setting, file permission issue, startup-time application throw, or a binding / DNS problem.

On Adaptive Web Hosting's ASP.NET Core plans, pre-installed runtimes, correct app pool defaults, and Plesk-managed permissions eliminate most platform-level causes — the rest is application code or configuration, and the diagnostic flow above finds the issue within minutes. Every plan includes a 30-day money-back guarantee. View hosting plans, see our complete deployment walkthrough, or talk to an ASP.NET expert.

Back to Blog