How to Deploy ASP.NET Applications on Windows Server 2025
Deploying ASP.NET applications to Windows Server 2025 requires a solid understanding of two distinct hosting models: the legacy .NET Framework pipeline hosted entirely within IIS, and the modern ASP.NET Core model that can run in-process within IIS or as a standalone Kestrel process behind IIS acting as a reverse proxy. Windows Server 2025 ships with IIS 10.0 and full support for both deployment strategies. This tutorial covers publishing .NET Framework ASP.NET Web Forms and MVC applications using Web Deploy and MSDeploy, configuring application pools for the correct .NET version, deploying ASP.NET Core applications with the .NET Hosting Bundle, configuring web.config for in-process and out-of-process hosting, managing connection strings with environment variables, enforcing HTTPS redirection, transforming web.config per environment, and diagnosing common errors such as 503.0 and 502.5.
Prerequisites
- Windows Server 2025 with IIS installed and the Web Server (IIS) role enabled
- .NET Framework 4.8.1 Windows Feature enabled for classic ASP.NET applications
- .NET Hosting Bundle installed for ASP.NET Core applications
- Web Deploy 4.0 (MSDeploy) installed on the server
- Visual Studio 2022 or the dotnet CLI on the developer workstation
- Administrative privileges on the server
Step 1: Install IIS and Required Role Services
Install IIS with the features needed to host both ASP.NET Framework and ASP.NET Core applications.
# Install IIS with ASP.NET and management tools
Install-WindowsFeature -Name Web-Server -IncludeManagementTools
Install-WindowsFeature -Name Web-Asp-Net45
Install-WindowsFeature -Name Web-ISAPI-Ext, Web-ISAPI-Filter
Install-WindowsFeature -Name Web-Net-Ext45
Install-WindowsFeature -Name Web-Mgmt-Console, Web-Mgmt-Service
# Enable the Web Management Service for remote Web Deploy connections
Set-Service -Name WMSVC -StartupType Automatic
Start-Service -Name WMSVC
# Verify IIS is running
Get-Service -Name W3SVC
Step 2: Install the .NET Hosting Bundle for ASP.NET Core
The .NET Hosting Bundle installs the .NET Runtime, ASP.NET Core Runtime, and the IIS ASP.NET Core Module (ANCM) — all required to host ASP.NET Core applications in IIS.
# Download the .NET 8 Hosting Bundle
$bundleUrl = "https://dot.net/v1/dotnet-install.ps1"
# For server deployments, use the official bundle installer from:
# https://dotnet.microsoft.com/en-us/download/dotnet/8.0
# Run the MSI silently:
$hostingBundle = "dotnet-hosting-8.0.10-win.exe"
Start-Process -FilePath $hostingBundle -ArgumentList "/quiet /norestart" -Wait
# Restart IIS to load the ASP.NET Core Module
net stop was /y
net start w3svc
# Verify the ASP.NET Core Module is registered
Get-WebConfiguration -PSPath "IIS:" -Filter "system.webServer/globalModules/*" |
Where-Object { $_.name -like "*AspNetCore*" }
Step 3: Deploy a .NET Framework ASP.NET Application with Web Deploy
Web Deploy (MSDeploy) is the standard mechanism for publishing classic ASP.NET Web Forms and MVC applications from Visual Studio or the command line. On the server, the Web Deployment Agent Service must be running.
# Install Web Deploy 4.0 via winget
winget install Microsoft.WebDeploy --silent --accept-package-agreements
# Create the IIS website and application pool for .NET Framework
Import-Module WebAdministration
New-WebAppPool -Name "MyAspNetPool"
Set-ItemProperty "IIS:AppPoolsMyAspNetPool" -Name managedRuntimeVersion -Value "v4.0"
Set-ItemProperty "IIS:AppPoolsMyAspNetPool" -Name managedPipelineMode -Value "Integrated"
New-Website -Name "MyAspNetSite" -PhysicalPath "C:inetpubMyAspNetApp" `
-ApplicationPool "MyAspNetPool" -Port 80
# Deploy from the developer workstation using msdeploy.exe
# Run this on the workstation, not the server:
# msdeploy.exe -verb:sync `
# -source:package="C:PublishMyAspNetApp.zip" `
# -dest:auto,computerName="https://myserver:8172/msdeploy.axd?site=MyAspNetSite",`
# authType=NTLM `
# -setParam:name="IIS Web Application Name",value="MyAspNetSite"
# Alternatively, use xcopy deployment for simple apps
$publishPath = "C:BuildMyAspNetApp"
$targetPath = "C:inetpubMyAspNetApp"
robocopy $publishPath $targetPath /MIR /XD ".git" /XF "*.user" /NP /LOG:"C:Logsdeploy.log"
Step 4: Deploy an ASP.NET Core Application to IIS
ASP.NET Core applications publish as self-contained folders. The web.config generated at publish time instructs IIS and the ASP.NET Core Module how to launch and manage the process.
# Publish the ASP.NET Core app on the development machine
dotnet publish "C:SourceMyAppMyApp.csproj" `
--configuration Release `
--output "C:PublishMyApp" `
--runtime win-x64 `
--self-contained false
# Copy published output to the server
robocopy "C:PublishMyApp" "C:inetpubMyAppCore" /MIR /NP
# Create a No Managed Code application pool for ASP.NET Core
Import-Module WebAdministration
New-WebAppPool -Name "MyAppCorePool"
Set-ItemProperty "IIS:AppPoolsMyAppCorePool" -Name managedRuntimeVersion -Value ""
New-Website -Name "MyAppCoreSite" -PhysicalPath "C:inetpubMyAppCore" `
-ApplicationPool "MyAppCorePool" -Port 8080
Step 5: Configure web.config for In-Process and Out-of-Process Hosting
The web.config file controls whether ASP.NET Core runs inside the IIS worker process (in-process, better performance) or as a separate Kestrel process (out-of-process, better isolation).
# In-process web.config (default for ASP.NET Core 3+, highest performance)
$inProcess = @'
'@
$inProcess | Set-Content -Path "C:inetpubMyAppCoreweb.config" -Encoding UTF8
# Out-of-process web.config (Kestrel runs as a separate process)
$outOfProcess = @'
'@
Step 6: Configure Connection Strings Using Environment Variables
Storing connection strings in web.config is a security risk. The preferred approach for ASP.NET Core is environment variables, which override appsettings.json values automatically through the default configuration pipeline.
# Set connection string as a system environment variable
# ASP.NET Core maps ConnectionStrings__DefaultConnection to
# Configuration["ConnectionStrings:DefaultConnection"]
[Environment]::SetEnvironmentVariable(
"ConnectionStrings__DefaultConnection",
"Server=sql01;Database=MyAppDb;Integrated Security=True;TrustServerCertificate=True;",
"Machine"
)
# Set via IIS application pool environment variables (scoped to that pool)
Import-Module WebAdministration
$appPoolPath = "IIS:AppPoolsMyAppCorePool"
$config = Get-WebConfiguration -PSPath $appPoolPath -Filter "system.applicationHost/applicationPools/add[@name='MyAppCorePool']/environmentVariables"
Add-WebConfiguration -PSPath $appPoolPath `
-Filter "system.applicationHost/applicationPools/add[@name='MyAppCorePool']/environmentVariables" `
-Value @{ name = "ASPNETCORE_ENVIRONMENT"; value = "Production" }
# Restart the app pool to apply changes
Restart-WebAppPool -Name "MyAppCorePool"
Step 7: Enforce HTTPS Redirection
HTTPS redirection should be configured both in the ASP.NET Core pipeline and at the IIS level to ensure all HTTP traffic is upgraded before reaching the application.
# Configure HTTPS binding in IIS
$certThumbprint = (Get-ChildItem Cert:LocalMachineMy |
Where-Object { $_.Subject -like "*myapp.example.com*" } |
Select-Object -First 1).Thumbprint
New-WebBinding -Name "MyAppCoreSite" -Protocol https -Port 443 `
-HostHeader "myapp.example.com" -SslFlags 1
# Assign the certificate to the binding
$binding = Get-WebBinding -Name "MyAppCoreSite" -Protocol https
$binding.AddSslCertificate($certThumbprint, "My")
# Add an IIS URL Rewrite rule for HTTP-to-HTTPS redirect
$rewriteRule = @'
'@
# In Program.cs, ASP.NET Core enforces HTTPS internally:
# app.UseHttpsRedirection();
# app.UseHsts();
Step 8: Troubleshoot 503.0 and 502.5 Errors
Two of the most common deployment errors are HTTP 503.0 (Service Unavailable, application pool stopped) and HTTP 502.5 (Bad Gateway, process startup failure). Knowing where to look accelerates diagnosis significantly.
# --- Diagnosing HTTP 503.0: Application Pool Stopped ---
# Check the state of all application pools
Get-WebConfiguration -PSPath "IIS:" `
-Filter "system.applicationHost/applicationPools/add" |
Select-Object name, @{n="State"; e={ (Get-WebAppPoolState -Name $_.name).Value }}
# Check Windows Event Log for rapid-fail protection events
Get-EventLog -LogName System -Source "WAS" -Newest 20
# Re-enable an application pool that stopped due to rapid fail
Start-WebAppPool -Name "MyAppCorePool"
# Increase rapid-fail protection threshold to allow more restarts
Set-ItemProperty "IIS:AppPoolsMyAppCorePool" `
-Name failure.rapidFailProtectionMaxCrashes -Value 10
# --- Diagnosing HTTP 502.5: ANCM Out-of-Process Startup Failure ---
# Enable stdout logging in web.config (set stdoutLogEnabled="true")
# Check the logs directory for startup errors
Get-Content "C:inetpubMyAppCorelogsstdout*.log" -Tail 50
# Check Application Event Log for ANCM errors
Get-EventLog -LogName Application -Source "IIS AspNetCore Module V2" -Newest 20
# Test the application runs directly from PowerShell
Set-Location "C:inetpubMyAppCore"
dotnet .MyApp.dll
# Verify correct .NET runtime is installed
dotnet --list-runtimes
Conclusion
Deploying ASP.NET applications on Windows Server 2025 is well-supported through a combination of IIS, the ASP.NET Core Module, and Web Deploy tooling. Classic .NET Framework applications run in managed IIS application pools with the pipeline set to Integrated mode, while ASP.NET Core applications require a No Managed Code pool and the .NET Hosting Bundle. Using environment variables for connection strings keeps secrets out of version-controlled configuration files. HTTPS enforcement should be applied both at the IIS binding level and within the ASP.NET Core middleware pipeline using UseHttpsRedirection(). When errors occur, the IIS application event log, ANCM stdout logs, and the application pool state are always the first places to investigate. Mastering these deployment fundamentals gives you a repeatable, reliable process for bringing .NET applications into production on Windows Server 2025.