How to Set Up ASP.NET Core with IIS on Windows Server 2022

ASP.NET Core applications are self-contained by default and use their own built-in Kestrel web server. When deploying to IIS on Windows Server 2022, IIS can act as a reverse proxy in front of Kestrel (out-of-process hosting) or host the application directly within the IIS worker process (in-process hosting). Both modes require the ASP.NET Core Module (ANCM), which is installed as part of the .NET Hosting Bundle. This guide covers the complete deployment workflow from runtime installation to production configuration.

Installing the .NET Hosting Bundle

The .NET Hosting Bundle is a single installer package that installs the .NET Runtime, ASP.NET Core Runtime, and most importantly the ASP.NET Core Module (ANCM) for IIS. You must install this bundle — not just the .NET Runtime — to host ASP.NET Core applications in IIS.

Download the Hosting Bundle from the .NET download page at https://dotnet.microsoft.com/en-us/download. Choose the current LTS or STS release (e.g., .NET 8.0). Install it from an elevated command prompt or PowerShell:

# Download .NET 8 Hosting Bundle (adjust URL to current version)
$url = "https://dot.net/v1/dotnet-install.sh"  # Use actual direct download URL
# For automated server provisioning:
Invoke-WebRequest -Uri "https://download.visualstudio.microsoft.com/download/pr/GUID/dotnet-hosting-8.0.6-win.exe" -OutFile "C:dotnet-hosting.exe"

# Install silently
Start-Process "C:dotnet-hosting.exe" -ArgumentList "/quiet /norestart" -Wait

After installation, verify the .NET runtime and ANCM are available:

# Check installed .NET runtimes
dotnet --list-runtimes

# Verify ANCM is registered with IIS
Get-WebGlobalModule -Name "AspNetCoreModuleV2"

If the module does not appear, IIS may have been running during installation. Restart IIS to pick up the new module:

net stop was /y
net start w3svc

ASP.NET Core Module (ANCM)

The ASP.NET Core Module V2 (AspNetCoreModuleV2) is a native IIS module that enables IIS to host ASP.NET Core applications. In in-process hosting mode, ANCM hosts the .NET application inside the IIS worker process (w3wp.exe). In out-of-process mode, ANCM acts as a reverse proxy, forwarding requests to a separately running Kestrel process.

ANCM is configured through the application’s web.config file. Every ASP.NET Core application deployed to IIS must have a web.config with an aspNetCore handler pointing to the application’s startup executable.

Publishing an ASP.NET Core Application

Before deploying to IIS, publish your ASP.NET Core application using the .NET SDK. There are two publish modes: framework-dependent (requires .NET installed on server) and self-contained (includes the runtime in the output folder).

Framework-dependent deployment (FDD) — smaller output, requires .NET runtime on server:

# Publish framework-dependent for Windows x64
dotnet publish MyApp.csproj -c Release -r win-x64 --self-contained false -o C:publishmyapp

Self-contained deployment (SCD) — includes runtime, no server dependency:

# Publish self-contained for Windows x64
dotnet publish MyApp.csproj -c Release -r win-x64 --self-contained true -o C:publishmyapp

The publish output contains all application DLLs, static assets, and a generated web.config file. The generated web.config is the key configuration file for IIS.

The web.config for ASP.NET Core

The auto-generated web.config tells IIS to use ANCM to host the application. For in-process hosting (the default and recommended for performance), it looks like this:



  
    
      
        
      
      
        
          
        
      
    
  

For a self-contained deployment (no dotnet.exe needed), set processPath to the application executable directly:

In-Process vs Out-of-Process Hosting

In-process hosting (hostingModel="inprocess") runs the application inside w3wp.exe. This is faster because there is no inter-process communication. Requests are handled directly within the IIS worker process. The downside is that if the .NET application crashes, the IIS application pool crashes with it and must be recycled.

Out-of-process hosting (hostingModel="outofprocess") runs Kestrel as a separate process. ANCM starts the Kestrel process and proxies requests to it. IIS stability is independent of application crashes. Out-of-process mode has slightly more overhead but provides better isolation:


  
    
    
  

In out-of-process mode, Kestrel listens on localhost on the configured port, and IIS forwards external requests to it.

Configuring the Application Pool

ASP.NET Core applications require the application pool to have its CLR version set to No Managed Code. This is because ASP.NET Core manages the .NET runtime itself — the classic IIS CLR loader is not needed and must be disabled to avoid conflicts:

# Create a dedicated application pool with No Managed Code
New-WebAppPool -Name "MyAspNetCorePool"
Set-ItemProperty -Path "IIS:AppPoolsMyAspNetCorePool" -Name "managedRuntimeVersion" -Value ""

# Verify the setting
(Get-ItemProperty -Path "IIS:AppPoolsMyAspNetCorePool").managedRuntimeVersion
# Should return empty string

Also configure the pool to recycle when the application crashes, and set appropriate memory and CPU limits:

# Enable rapid fail protection — if app crashes too many times, stop the pool
Set-ItemProperty -Path "IIS:AppPoolsMyAspNetCorePool" -Name "failure.rapidFailProtection" -Value $true
Set-ItemProperty -Path "IIS:AppPoolsMyAspNetCorePool" -Name "failure.rapidFailProtectionInterval" -Value 5
Set-ItemProperty -Path "IIS:AppPoolsMyAspNetCorePool" -Name "failure.rapidFailProtectionMaxCrashes" -Value 5

# Configure action on worker process failure
Set-ItemProperty -Path "IIS:AppPoolsMyAspNetCorePool" -Name "failure.loadBalancerCapabilities" -Value "HttpLevel"

Deploying the Application to IIS

Create the IIS website pointing to the published output directory:

$sitePath = "C:inetpubmyapp"
New-Item -Path $sitePath -ItemType Directory -Force

# Copy published output to deployment directory
Copy-Item -Path "C:publishmyapp*" -Destination $sitePath -Recurse -Force

# Grant IIS app pool identity access to the deployment directory
icacls $sitePath /grant "IIS AppPoolMyAspNetCorePool:(OI)(CI)F"

# Create the IIS website
New-WebSite -Name "MyAspNetCoreApp" -PhysicalPath $sitePath -Port 80 -HostHeader "app.example.com" -ApplicationPool "MyAspNetCorePool"

Create the logs directory with write permissions (required for stdoutLog):

New-Item -Path "$sitePathlogs" -ItemType Directory -Force
icacls "$sitePathlogs" /grant "IIS AppPoolMyAspNetCorePool:(OI)(CI)M"

Configuring Environment Variables

ASP.NET Core reads its configuration from environment variables, appsettings.json, and other sources. The ASPNETCORE_ENVIRONMENT variable controls which appsettings file is loaded (appsettings.Production.json, etc.). Set environment variables in web.config (shown above) or directly on the application pool via IIS:

# Set environment variables on the application pool (IIS 10+)
$env = [Microsoft.Web.Administration.ServerManager]::new()
$pool = $env.ApplicationPools["MyAspNetCorePool"]
$pool.SetAttributeValue("environmentVariables", "")

# More commonly, set via web.config environmentVariables section or
# configure connection strings via Windows environment variables
[System.Environment]::SetEnvironmentVariable("ConnectionStrings__Default", "Server=db;Database=mydb;Trusted_Connection=True;", "Machine")

Sensitive values like connection strings should be set as machine-level environment variables rather than stored in web.config or appsettings.json in source control.

Enabling stdout Logging for Troubleshooting

If the application fails to start, enable stdout logging temporarily in web.config to capture startup errors. This writes the application’s console output to a file:

Check the log files in the logs directory after attempting to start the application. Disable stdout logging once the issue is resolved — it has a performance impact and generates large log files under load.

Deploying via Web Deploy (MsDeploy)

Web Deploy (MsDeploy) enables pushing ASP.NET Core applications from a build server or development machine to IIS without manual file copying. Install Web Deploy on the server:

# Install Web Deploy via Windows Package Manager or manual download
winget install Microsoft.WebDeploy

# Or install via PowerShell with Web Platform Installer (deprecated but functional)
# Download Web Deploy 4.0 MSI from https://www.iis.net/downloads/microsoft/web-deploy

Publish to IIS from the development machine using the .NET publish command with a Web Deploy publish profile:

# Publish directly to remote IIS via MSDeploy
dotnet publish MyApp.csproj -c Release /p:PublishProfile=MyServerProfile

# Or use msdeploy.exe directly
msdeploy.exe -verb:sync `
    -source:contentPath="C:publishmyapp" `
    -dest:contentPath="MyAspNetCoreApp",computerName="https://myserver:8172/msdeploy.axd",userName="administrator",password="Password",authType="Basic" `
    -allowUntrusted

Health Checks

ASP.NET Core includes built-in health check middleware. Register health checks in Program.cs and expose a health endpoint that IIS, load balancers, or monitoring systems can poll:

// In Program.cs
builder.Services.AddHealthChecks()
    .AddSqlServer(builder.Configuration.GetConnectionString("Default"))
    .AddCheck("self", () => HealthCheckResult.Healthy());

app.MapHealthChecks("/health", new HealthCheckOptions
{
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});

Configure IIS Application Request Routing (ARR) to poll the health endpoint and remove unhealthy servers from rotation in a load-balanced deployment. You can also configure the IIS application pool to monitor the health endpoint and recycle the pool if it returns unhealthy responses repeatedly.

ASP.NET Core hosted on IIS with Windows Server 2022 delivers excellent performance, tight Windows integration, and mature tooling. Use in-process hosting for maximum throughput, set the app pool to No Managed Code, configure environment variables securely, and leverage Web Deploy for repeatable deployments across environments.