Remigiusz ZalewskiRemigiusz Zalewski

How to deploy a .NET Web API in IIS

deploy-dotnet-web-api-iis

Introduction

Not every company is on Azure, and IIS on Windows Server is still the default for a lot of internal .NET APIs. This video walks the full IIS deployment: installing the right components on a clean server, publishing the app, and configuring an application pool the way ASP.NET Core actually needs it configured - not the ASP.NET Framework defaults IIS assumes by default.

🎬 Watch the full video here:


The Hosting Bundle is not optional

A default Windows Server install has IIS, but IIS doesn't know how to run ASP.NET Core out of the box - that requires the .NET Hosting Bundle, which installs the .NET runtime plus the ASP.NET Core Module (ANCM), an IIS module that proxies incoming requests into your app's Kestrel server running behind it:

https://dotnet.microsoft.com/download/dotnet/<version>
→ ASP.NET Core Runtime <version> Hosting Bundle

Skipping this and only installing the plain .NET runtime is the single most common reason a fresh IIS deploy returns a generic 500.19 or 502.5 error with no obvious cause.

The application pool: no Managed Code, please

IIS application pools default to a .NET CLR version and "Managed" pipeline mode - both leftovers from classic ASP.NET, and both wrong for ASP.NET Core, which manages its own runtime entirely outside IIS's CLR hosting:

  • .NET CLR Version: No Managed Code
  • Managed Pipeline Mode: doesn't matter once CLR version is "No Managed Code," but Integrated is the safe default

ANCM handles process management and forwards requests; IIS's own managed pipeline never touches your app's code at all.

web.config is what wires ANCM to your app

dotnet publish generates a web.config alongside the published output that tells IIS how to start the app:

<aspNetCore processPath="dotnet" arguments=".\MyApi.dll" stdoutLogEnabled="false" />

If this file is missing or malformed after publish, IIS has no idea what to launch - this is worth checking by hand the first time a deploy doesn't come up.

Turn on stdout logging when something's wrong

The fastest way to actually see a startup exception, rather than guessing at IIS's opaque error codes, is flipping stdoutLogEnabled to true temporarily:

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

The logs folder needs to exist and be writable by the app pool identity, or this silently does nothing.

App pool identity and folder permissions

The app pool runs under an identity (ApplicationPoolIdentity by default) that needs read access to the published files and write access to the log folder. A deploy that "works" until the first write to disk, then throws an access-denied error, is almost always a permissions gap on that identity rather than a code bug.

Common pitfalls

  • Installing the .NET Runtime instead of the Hosting Bundle. The Hosting Bundle is the one that actually adds ANCM.
  • Leaving the app pool on "Managed Code." ASP.NET Core needs "No Managed Code" - it's not using IIS's CLR hosting at all.
  • A missing or hand-edited-incorrectly web.config. It's the only thing telling IIS what to launch.
  • App pool identity lacking write access to the log folder - stdoutLogEnabled then does nothing and you're debugging blind.
  • Forgetting to recycle the app pool after a redeploy and wondering why the old version is still responding.

Common pitfalls

  • Not opening the Windows Firewall port the site is bound to, then wondering why it's unreachable from outside the server itself.

Key Takeaways

  • Install the .NET Hosting Bundle, not just the runtime - it's what adds the ASP.NET Core Module IIS needs.
  • Set the app pool's .NET CLR Version to "No Managed Code"; ASP.NET Core manages its own runtime.
  • web.config, generated by dotnet publish, is what tells IIS how to launch the app via ANCM.
  • Enable stdoutLogEnabled (with a writable log folder) to see the real startup exception instead of a generic IIS error code.
  • Check app pool identity permissions before assuming a deploy failure is a code bug.

Get the Full Source Code

The complete runnable solution - the API and its generated web.config - is available to Patreon supporters. If you want the exact IIS and app pool settings used instead of rebuilding them from the walkthrough above, you can find it on Patreon.

Resources