ASP.NET Core apps run on IIS through the ASP.NET Core Module (ANCM), which either hosts the app in-process inside the IIS worker (fastest), or forwards requests to the app’s own Kestrel server (out-of-process). Setting it up takes a few steps. Most deployment errors come from a missing runtime, permissions, or configuration.

1. Install IIS and the Hosting Bundle

Install-WindowsFeature Web-Server -IncludeManagementTools

Then download and install the .NET Hosting Bundle for your app’s .NET version (for example .NET 8 or 9) from Microsoft’s .NET download page. It installs the .NET runtime, the ASP.NET Core runtime, and the ASP.NET Core Module. Restart IIS afterwards:

iisreset

If you installed the bundle before IIS, run the bundle’s installer again with “Repair”. See install IIS.

2. Publish the app

On your build machine (or in CI):

dotnet publish -c Release -o ./publish
  • Framework-dependent (default): uses the runtime from the Hosting Bundle. That makes a smaller deployment.
  • Self-contained (--self-contained -r win-x64): includes the runtime, for when you can’t install runtimes on the server.

Copy the publish folder to the server, for example C:\Sites\myapp. The publish output includes a generated web.config.

3. Create the app pool and site

Import-Module WebAdministration
New-WebAppPool -Name "myapp"
Set-ItemProperty IIS:\AppPools\myapp -Name managedRuntimeVersion -Value ""     # "No Managed Code"
New-Website -Name "myapp" -PhysicalPath "C:\Sites\myapp" -ApplicationPool "myapp" -HostHeader "app.example.com" -Port 80
  • The app pool must use No Managed Code: ASP.NET Core doesn’t use the classic .NET CLR pipeline.
  • Give the app pool identity read access to the folder, and write access only where needed (logs, uploads):
icacls "C:\Sites\myapp" /grant "IIS AppPool\myapp:(OI)(CI)RX"
icacls "C:\Sites\myapp\logs" /grant "IIS AppPool\myapp:(OI)(CI)M"

4. The web.config

The generated file looks like this:

<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="false"
                  stdoutLogFile=".\logs\stdout" hostingModel="inprocess">
        <environmentVariables>
          <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" />
        </environmentVariables>
      </aspNetCore>
    </system.webServer>
  </location>
</configuration>
  • hostingModel="inprocess" (the default) gives the best performance.
  • Put secrets (connection strings, API keys) in environment variables or a secure store, not in source-controlled appsettings.json. See secrets management.

5. HTTPS

Get a free certificate with win-acme, and redirect HTTP to HTTPS. See IIS SSL certificates. If the app uses UseHttpsRedirection() and UseForwardedHeaders(), make sure they work correctly behind IIS. With in-process hosting, IIS passes the scheme correctly.

Troubleshooting

Error Usual cause
HTTP 500.19 ASP.NET Core Module not installed (Hosting Bundle missing, or installed before IIS)
HTTP 500.30 – In-process start failure App crashed at startup: bad config, missing connection string, exception in Program.cs
HTTP 500.31 / 500.32 Wrong .NET runtime version installed, or bitness mismatch
HTTP 502.5 (out-of-process) The app process failed to start
403 / 404 on static files Folder permissions, or wwwroot not published

To see startup errors, temporarily set stdoutLogEnabled="true" (create the logs folder), reproduce the error, read the log, then switch it off. Also check Event Viewer → Windows Logs → Application for “IIS AspNetCore Module” events. See Windows Event Viewer errors.

Deploying updates without file locks

Running apps lock their DLLs. To deploy:

  1. Drop an app_offline.htm file into the site folder. ANCM shuts the app down, and serves that page.
  2. Copy the new files.
  3. Delete app_offline.htm.

Web Deploy (msdeploy), or CI pipelines (GitHub Actions with a self-hosted runner), can automate this.

Quick reference

Step Action
Runtime .NET Hosting Bundle, then iisreset
Build dotnet publish -c Release
App pool No Managed Code
Hosting model inprocess (default)
Startup errors stdout log + Event Viewer
Updates app_offline.htm

Related: IIS URL Rewrite and ARR and Windows VPS.

  • IIS
  • .NET
  • Windows Server