Internet Information Services (IIS) is still the most direct way to host ASP.NET Core in production on Windows. ASP.NET Core applications run as self-contained Kestrel processes, and IIS acts as a reverse proxy in front of them through the ASP.NET Core Module — it terminates HTTPS, supervises the worker process, and recycles the application pool on your schedule. Once you understand that two-process model, deployment stops being mysterious.
Hosting cost, CPU allocation, and RAM limits determine how well that model performs under real traffic, so compare Windows VPS providers on our comparison table and size the plan for your expected concurrent requests before you deploy.
Install the .NET 8 Hosting Bundle on Windows Server
The .NET 8 Hosting Bundle contains the ASP.NET Core runtime, the .NET runtime, and the native IIS module. Download it from the official .NET site and install silently, or verify an existing install with:
dotnet-hosting-8.0.x-win.exe /install /quiet /norestart
dotnet --list-runtimes
%windir%\system32\inetsrvppcmd list modules | findstr AspNetCoreModule
The last command should show both AspNetCoreModule and AspNetCoreModuleV2. If the module is missing, the site will return 500.19 errors — a reboot after the bundle install clears most of those, since the installer registers the native module with IIS at boot time.
Publish the Application and Set Folder Permissions
Publish a framework-dependent build to a folder outside the web root, then grant the application pool identity read and execute rights on that folder. The pool identity is named IIS AppPool\<poolname>:
dotnet publish -c Release -o D:\sites\myapp
icacls D:\sites\myapp /grant "IIS AppPool\myapppool:(OI)(CI)RX" /T
If the app writes files (uploads, logs, cache), grant (OI)(CI)M to a subfolder instead of the whole tree, and keep the database connection strings in environment variables or the Secret Manager rather than in the published appsettings.json.
Create the IIS Site and Application Pool
ASP.NET Core does not use the .NET Framework CLR, so the application pool must run with No Managed Code. Create the pool and site with appcmd, which is scriptable and idempotent:
appcmd add apppool /name:myapppool /managedRuntimeVersion:"" /startMode:AlwaysRunning
appcmd add site /name:myapp /physicalPath:D:\sites\myapp `
/bindings:http/*:80:myapp.example.com
appcmd set app /app.name:myapp/ /applicationPool:myapppool
startMode:AlwaysRunning and the auto-start provider keep the first request from paying a cold-start penalty — the worker process starts when the server boots instead of when the first visitor arrives. The equivalent GUI path is IIS Manager > Application Pools > Advanced Settings.
Bind HTTPS with a Certificate
HTTPS termination belongs in IIS, with Kestrel left on HTTP behind localhost. For production, obtain a certificate from a public CA (the win-acme tool automates Let’s Encrypt renewals) and bind it to the site. For a quick test environment, a self-signed certificate works:
New-SelfSignedCertificate -DnsName myapp.example.com `
-CertStoreLocation cert:\LocalMachine\My
appcmd set site /site.name:myapp /+bindings.[protocol='https',bindingInformation='*:443:myapp.example.com']
Then select the certificate in IIS Manager’s site bindings. Enable HTTP/2 on the binding (IIS supports it out of the box on Windows Server 2022/2025 with TLS 1.2+), and set the HTTPS redirect rule so plain-HTTP requests bounce to the secure endpoint. Keep TLS 1.2 and 1.3 enabled and disable legacy protocols system-wide.
Configure the ASP.NET Core Module and Process Management
The web.config in the published output controls how IIS launches and supervises the process. The critical element is aspNetCore, where hostingModel="inprocess" runs the app inside the IIS worker process (lower overhead) and the out-of-process model runs a separate Kestrel process:
<aspNetCore processPath="dotnet" arguments=".\myapp.dll"
hostingModel="inprocess" stdoutLogEnabled="true"
stdoutLogFile=".\logs\stdout" startupTimeLimit="120">
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" />
</environmentVariables>
</aspNetCore>
startupTimeLimit and shutdownTimeLimit control how long IIS waits for the app to start and stop before recycling the process — raise them for apps that take more than a few seconds to warm up. Keep stdoutLogEnabled="true" during the first weeks of operation so startup exceptions land in a readable log file instead of a generic 500 response.
Monitor Worker Processes and App Pool Recycling
Process management is where IIS earns its keep. Check which worker processes are alive, what they consume, and recycle the pool on demand or on a schedule:
appcmd list wp
Get-Process -Name myapp | Select-Object Id, CPU, WorkingSet
appcmd recycle apppool /apppool.name:myapppool
appcmd set apppool /apppool.name:myapppool /recycling.periodicRestart.time:00:00:00 /recycling.periodicRestart.privateMemory:1048576
The last command recycles the pool when it exceeds 1 GB of private memory — a blunt but effective guard against memory leaks. For deeper diagnosis, enable Failed Request Tracing on the site, correlate the aspNetCore module’s 500.x errors with the stdout log, and watch the Windows Application log for Event ID 1000 (unhandled crash) and Event ID 1010 (startup failure) entries from the module.
Summary
Hosting ASP.NET Core on IIS comes down to five decisions: install the hosting bundle, publish with the right folder permissions, run a No Managed Code pool, terminate HTTPS in IIS, and tune the module’s process settings. Do those, and IIS gives you recycling, supervision, and logging that a bare Kestrel process lacks. To size a Windows VPS with the CPU and RAM your .NET workload needs, see the full specs and pricing on the Windows VPS comparison table.
Vultr’s high-performance cloud compute instances deploy Windows Server in about a minute — create a Vultr Windows VPS and start your ASP.NET Core deployment today.
