run your softwareintermediate18 min read·Updated Apr 30, 2026

How to Host ASP.NET Core 10 on IIS — Windows Server Guide (2026)

Step-by-step guide to hosting ASP.NET Core 10 apps on IIS on a Windows Server VPS. Covers IIS install, .NET 10 Hosting Bundle, app pool config, deployment, and common errors. Tested end-to-end on Raff.

[HUMAN-REQUIRED: alt text — e.g. 'IIS Manager showing a configured ASP.NET Core site with No Managed Code app pool']
On this page

Don't have a Windows Server yet?

Deploy Windows Server 2019/2022/2025 in ~2 minutes. 6-month evaluation licence included.

Deploy Windows now

In short

Hosting an ASP.NET Core 10 app on IIS takes 5 steps: install the Web Server (IIS) role, install the .NET 10 Hosting Bundle (Runtime + ASP.NET Core Module V2), publish your app with dotnet publish, create an IIS site with an app pool set to No Managed Code, then grant the app pool identity read access to the published folder. Whole flow runs in under 10 minutes on a fresh Windows Server 2025 install. .NET 10 is the current LTS (supported through November 10, 2028); .NET 9 is STS (ends November 10, 2026, same date as .NET 8 LTS). New deployments should target .NET 10.

Who this guide is for

You want to host an ASP.NET Core web app on Windows Server with IIS as the front-end web server. Common scenarios:

  • A small business app (intranet, customer portal, dashboard) that needs to run on a single Windows Server
  • An ASP.NET Core API behind IIS (so IIS handles HTTPS, static assets, and request routing while your app handles business logic)
  • Multiple ASP.NET Core sites on one server, each with their own IIS bindings and app pools
  • Replacing an aging ASP.NET Framework deployment with a modern .NET 10 codebase

If you're hosting at scale (multiple servers, load balancers, container orchestration), Kestrel-only behind a reverse proxy (NGINX, YARP) is usually a better fit than IIS. This guide is for the very common single-server-or-small-cluster case where IIS gives you HTTPS termination, static asset serving, and process management for free.

What you'll need

  • A Raff Windows Server 2025 — this guide tested on the Production plan ($35.99 — 4 vCPU / 8 GB RAM / 120 GB NVMe), which is the right size for production small-traffic deployments. Single-site staging works fine on the Small Business plan ($19.99). For 10+ sites or high-traffic production, step up to Heavy Workload ($63.99) or Enterprise ($127.99).
  • Local administrator access to the Raff Server via RDP. See our RDP connection guide if you haven't connected before.
  • An ASP.NET Core app to deploy. We'll create one as part of this walkthrough using dotnet new webapp.
  • Optional but recommended: a separate machine for building (your laptop, a CI runner) — production servers should host apps, not compile them. We cover both single-server and split build/host scenarios.
  • Estimated time: 30-45 minutes for first deploy, 5 minutes for subsequent deployments to the same server.

What changed in .NET 10 / 2026

If you're coming from older guides written for .NET 6, 7, 8, or 9, three things are different:

  1. .NET 10 LTS is the current target. Released November 11, 2025. Supported until November 10, 2028. .NET 9 is STS (Standard Term Support) and ends November 10, 2026 — same date as .NET 8 LTS, because Microsoft extended STS from 18 to 24 months. For new deployments, pick .NET 10.
  2. web.config defaults to launching YourApp.exe directly, not dotnet YourApp.dll. Performance is the same; the syntax is just simpler. If you've copy-pasted older web.config templates, both formats still work — but the auto-generated one in .NET 10 publishes uses the .exe form.
  3. Hosting Bundle and SDK are now separately installable via winget as Microsoft.DotNet.HostingBundle.10 and Microsoft.DotNet.SDK.10. No more browsing dotnet.microsoft.com and clicking through GUI installers (unless you prefer to).

Step 1 — Install the Web Server (IIS) role

Open PowerShell as administrator on the Raff Server and run:

powershellInstall-WindowsFeature -Name `
    Web-Server, `
    Web-Common-Http, `
    Web-Static-Content, `
    Web-Default-Doc, `
    Web-Dir-Browsing, `
    Web-Http-Errors, `
    Web-App-Dev, `
    Web-Net-Ext45, `
    Web-AppInit, `
    Web-ISAPI-Ext, `
    Web-ISAPI-Filter, `
    Web-Health, `
    Web-Http-Logging, `
    Web-Performance, `
    Web-Stat-Compression, `
    Web-Security, `
    Web-Filtering, `
    Web-Mgmt-Tools, `
    Web-Mgmt-Console `
    -IncludeManagementTools

This installs the IIS parent role plus the modules ASP.NET Core needs (Common Http, App Development, ASP.NET 4.8 compatibility, ISAPI filters/extensions for ANCM v2, request filtering, dynamic compression, and the management console).

Verify:

powershellGet-WindowsFeature -Name Web-Server | Format-Table Name, InstallState
Get-Service -Name W3SVC, WAS | Format-Table Name, Status, StartType

You should see Web-Server: Installed, W3SVC: Running, Automatic, WAS: Running, Manual. The Manual start type for WAS (Windows Process Activation Service) is correct — it's started on-demand by W3SVC, not directly. Don't change it.

Confirm IIS is serving requests by browsing http://localhost from Edge on the server:

IIS welcome page on Windows Server 2025 — first run after Install-WindowsFeature

If you see the multi-language "Internet Information Services" welcome page, IIS is alive.

Article finding from our test: Server 2025 IIS install completes without requiring a reboot. Older Windows Server versions sometimes prompted for one — Server 2025 doesn't. Install-WindowsFeature returned Success: True, RestartNeeded: No, ExitCode: Success on our test VM.

Step 2 — Install the .NET 10 Hosting Bundle

The Hosting Bundle is a single installer that puts down three things you need:

  • .NET Runtime (CoreCLR — the engine that runs managed code)
  • ASP.NET Core Runtime (Kestrel server, framework libraries)
  • ASP.NET Core Module V2 (ANCM v2 — the IIS module that hands HTTP requests off to your app's Kestrel process)

The fastest way is winget:

powershellwinget install --id Microsoft.DotNet.HostingBundle.10 --silent --accept-source-agreements --accept-package-agreements

That downloads ~112 MB and installs in 30-60 seconds. As of April 2026, you'll get version 10.0.7 (the latest patch).

If winget isn't available or you prefer a GUI install, download the bundle from dotnet.microsoft.com/download/dotnet/10.0 — pick the ASP.NET Core Runtime column → Hosting Bundle. The filename is dotnet-hosting-10.0.7-win.exe or similar. Run as administrator.

Refresh PATH in your current PowerShell session

The Hosting Bundle adds C:\Program Files\dotnet\ to the Machine PATH, but your existing PowerShell session won't see it until you reopen the window — or refresh the PATH manually:

powershell$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")

This is a common gotcha. If your dotnet command says "not recognized" right after installing, this is why.

Verify the runtime is registered

powershelldotnet --list-runtimes

You should see at minimum:

Microsoft.AspNetCore.App 10.0.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 10.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]

Verify ANCM is registered with IIS

powershellGet-WebGlobalModule -Name "AspNetCoreModuleV2" | Format-Table Name, Image

Expected output:

Name               Image
----               -----
AspNetCoreModuleV2 %ProgramFiles%\IIS\Asp.Net Core Module\V2\aspnetcorev2.dll

Article finding from our test: Older Microsoft docs warn that "if IIS was added after the Hosting Bundle, rerun or repair the bundle so ANCM is correctly registered." On Windows Server 2025 with the latest 10.0.7 Hosting Bundle, install order doesn't matter — ANCM registered correctly on our test VM regardless of whether IIS or the bundle went in first. The cautious advice is still valid for older bundles, but the 2026 reality is more forgiving.

Step 3 — Restart IIS (or skip — see below)

Microsoft's docs say "After the Hosting Bundle is installed, a manual IIS restart may be required." On a fresh install with no apps running yet, our test showed this is not strictly necessary — ANCM v2 was already loaded after the bundle install. But it costs you 3 seconds and removes any uncertainty:

powershelliisreset

Output: Internet services successfully stopped followed by Internet services successfully restarted.

If you have running ASP.NET Core apps already on the server, do run iisreset — it ensures all worker processes pick up the latest ANCM. For first-time installs, it's optional.

Step 4 — Publish an ASP.NET Core app

Two paths here. Pick whichever fits your workflow.

Path A — Build on the same server (dev/staging only)

Convenient for a single-server deployment, but not the production-correct pattern (production servers shouldn't have build tools installed). Useful for testing, dev/staging, or small-scale deployments.

You'll need the .NET 10 SDK — separate from the Hosting Bundle:

powershellwinget install --id Microsoft.DotNet.SDK.10 --silent --accept-source-agreements --accept-package-agreements

# Refresh PATH for the current session
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")

# Verify SDK is available
dotnet --list-sdks

You should see 10.0.203 [C:\Program Files\dotnet\sdk] (current SDK version as of April 2026).

Article finding from our test: This SDK-vs-runtime distinction is the #1 thing that trips up first-time IIS deployers. The Hosting Bundle has no SDK. The error message when you try dotnet new or dotnet publish without an SDK is helpful (No .NET SDKs were found.) but easy to miss if you assume the bundle gave you everything.

Now create and publish a sample app:

powershell# Make a working folder
New-Item -ItemType Directory -Path "C:\src\RaffSampleApp" -Force
Set-Location "C:\src\RaffSampleApp"

# Create a Razor Pages webapp targeting .NET 10
dotnet new webapp --framework net10.0 --name RaffSampleApp --output .

# Publish to deployment folder (framework-dependent, win-x64)
dotnet publish -c Release -o C:\publish\RaffSampleApp --runtime win-x64 --self-contained false

The first time you run any dotnet command after installing the SDK, you'll see a "Welcome to .NET 10.0!" message with telemetry information. The template will also auto-install an ASP.NET Core HTTPS development certificate — harmless and not used for IIS deployments, but worth knowing it ran.

Path B — Build elsewhere, copy to server (production pattern)

This is what you do for real production deployments. Build on a developer workstation, a CI/CD runner (GitHub Actions, Azure DevOps, GitLab CI), or a dedicated build server, then copy the published output to the Raff Server.

On your build machine:

powershell# From your project directory
dotnet publish -c Release -o C:\publish\YourApp --runtime win-x64 --self-contained false

Then transfer the C:\publish\YourApp folder contents to your Raff Server. Options:

  • RDP file transfer — copy from your local machine, paste into RDP session
  • robocopy over SMB if you have file-share access to the Raff Server
  • scp/rsync from WSL if you've enabled OpenSSH on the Raff Server
  • Git-based deploygit pull on the server, then dotnet publish locally (requires SDK on server)
  • Object storage dropaws s3 cp or similar, pull down on the server

Whichever method, the result is the same: your published files sit in a folder on the Raff Server, ready for IIS to point at.

Self-contained vs framework-dependent

We used --self-contained false above. This is framework-dependent publish — your app uses the .NET 10 runtime installed on the server via the Hosting Bundle. Output is small (~5-10 MB for a small app), and runtime updates are independent of your app deployments.

The alternative is --self-contained true:

powershelldotnet publish -c Release -o C:\publish\YourApp --runtime win-x64 --self-contained true

Larger output (~70-100 MB) — bundles the runtime with your app. Useful when:

  • The server has no Hosting Bundle (won't help if you also need IIS, since ANCM v2 needs the bundle)
  • You want to pin to an exact runtime version per-app
  • You're targeting servers you don't fully control

For most Raff Server deployments, stick with framework-dependent + Hosting Bundle. Easier upgrades, smaller deploys, faster builds.

What gets published

After dotnet publish, the output folder contains:

  • RaffSampleApp.exe — the launcher executable IIS will run
  • RaffSampleApp.dll — the actual app code
  • RaffSampleApp.deps.json, RaffSampleApp.runtimeconfig.json — runtime configuration
  • appsettings.json, appsettings.Development.json — your config files
  • web.config — auto-generated; tells IIS how to launch the app via ANCM
  • wwwroot\ — static files (CSS, JS, images)
  • RaffSampleApp.staticwebassets.endpoints.json — static asset routing manifest (.NET 10 specific)

What's in the auto-generated web.config?

xml<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath=".\RaffSampleApp.exe" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
    </system.webServer>
  </location>
</configuration>

Key things in there:

  • modules="AspNetCoreModuleV2" — the IIS handler that came with your Hosting Bundle
  • processPath=".\RaffSampleApp.exe" — IIS launches the exe directly (newer .NET pattern; older dotnet YourApp.dll form still works)
  • hostingModel="inprocess" — Kestrel runs inside the IIS w3wp.exe worker process. This is the default since .NET 3.0 and gives best performance. The alternative outofprocess runs Kestrel as a separate process behind IIS as a reverse proxy (slower; used for compatibility scenarios)
  • stdoutLogEnabled="false" — stdout logs disabled by default (perf reason). We'll show how to enable for debugging in the Common Errors section.
  • <location path="." inheritInChildApplications="false"> — prevents this app's config from leaking into nested child apps

You don't need to edit this file for our test. For real production apps, you might enable stdout logging during deploys, or add custom MIME types and headers.

Step 5 — Create the IIS site and app pool

Back on the Raff Server (whether you built locally or copied from elsewhere), create the IIS site:

powershellImport-Module WebAdministration

# Stop and delete the default IIS site (we want our app on port 80)
Stop-Website -Name "Default Web Site" -ErrorAction SilentlyContinue
Remove-Website -Name "Default Web Site" -ErrorAction SilentlyContinue

# Create the application pool
New-WebAppPool -Name "RaffSampleAppPool"

# Set No Managed Code (managedRuntimeVersion = "" is the magic for ASP.NET Core)
Set-ItemProperty -Path "IIS:\AppPools\RaffSampleAppPool" -Name "managedRuntimeVersion" -Value ""

# Create the IIS site pointing at our published folder
New-Website -Name "RaffSampleApp" `
    -PhysicalPath "C:\publish\RaffSampleApp" `
    -ApplicationPool "RaffSampleAppPool" `
    -Port 80

# Grant the app pool identity read access to the published folder
icacls "C:\publish\RaffSampleApp" /grant "IIS AppPool\RaffSampleAppPool:(OI)(CI)RX"

Why "No Managed Code"?

ASP.NET Core doesn't run on the .NET Framework CLR — it runs in its own out-of-process Kestrel server (or inside the IIS w3wp.exe worker process via in-process ANCM). Setting managedRuntimeVersion = "" tells IIS not to load the legacy .NET Framework CLR for this pool. If you forget this, you'll get an HTTP 500.30 error when ANCM tries to launch your app.

Permissions

icacls "..." /grant "IIS AppPool\RaffSampleAppPool:(OI)(CI)RX" grants the app pool's auto-generated virtual identity:

  • (OI) — Object Inherit (apply to files in the folder)
  • (CI) — Container Inherit (apply to subfolders)
  • RX — Read + Execute (read the files, execute RaffSampleApp.exe)

If your app needs to write files (logs, uploads, generated files), grant (M) Modify access — but only on the specific subfolder where it needs to write, never on the whole site folder. Example for a logs subfolder:

powershellicacls "C:\publish\RaffSampleApp\logs" /grant "IIS AppPool\RaffSampleAppPool:(OI)(CI)M"

Verify

powershellGet-Website -Name "RaffSampleApp" | Format-Table Name, State, PhysicalPath, Bindings
Get-IISAppPool -Name "RaffSampleAppPool" | Format-Table Name, State, ManagedRuntimeVersion
Invoke-WebRequest -Uri http://localhost -UseBasicParsing | Select-Object StatusCode, StatusDescription

Expected:

  • Site State: Started, PhysicalPath: C:\publish\RaffSampleApp
  • App pool State: Started, ManagedRuntimeVersion: (blank — that's correct)
  • HTTP StatusCode: 200, StatusDescription: OK

Now open Edge on the server and browse to http://localhost:

ASP.NET Core 10 Razor Pages welcome page on IIS — RaffSampleApp running on Windows Server 2025

You should see the RaffSampleApp welcome page with the navbar showing Home and Privacy links, the "Welcome" heading, and footer attribution. This means the full pipeline is working: browser → IIS → ANCM v2 → in-process Kestrel → ASP.NET Core 10 → 200 OK back.

Step 6 — Open the firewall for external access

So far we've only confirmed the app responds locally on the server. To reach it from your browser on another machine:

powershell# Open port 80 (HTTP) in Windows Firewall
New-NetFirewallRule `
    -DisplayName "HTTP (TCP 80)" `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 80 `
    -Action Allow `
    -Profile Any

# Open port 443 (HTTPS) too, for when you set up a TLS certificate
New-NetFirewallRule `
    -DisplayName "HTTPS (TCP 443)" `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 443 `
    -Action Allow `
    -Profile Any

For a real production deployment, don't leave port 80 open to Any — at minimum, set up a TLS certificate (Let's Encrypt via win-acme works the same way as we covered in our SQL Server hardening guide) and redirect HTTP → HTTPS. For admin endpoints or IIS Manager remote access, scope firewall rules to your admin IP.

Common errors

The five errors you'll most likely encounter, with solutions.

HTTP Error 500.30 — ASP.NET Core app failed to start

The most common ASP.NET Core on IIS error. Causes (in order of frequency):

  1. App pool has the wrong managedRuntimeVersion. Should be empty string (""), not v4.0. Re-run Set-ItemProperty -Path "IIS:\AppPools\YourPool" -Name "managedRuntimeVersion" -Value "".
  2. Hosting Bundle missing or wrong version. Confirm dotnet --list-runtimes shows the ASP.NET Core runtime your app was built against.
  3. App throws on startup. Could be a missing connection string, malformed appsettings.json, or any unhandled exception in Program.cs.

To see the actual exception, temporarily enable stdout logging in web.config:

xml<aspNetCore processPath=".\YourApp.exe"
            stdoutLogEnabled="true"
            stdoutLogFile=".\logs\stdout"
            hostingModel="inprocess">
</aspNetCore>

Create the logs folder and grant the app pool write access:

powershellNew-Item -ItemType Directory -Path "C:\publish\YourApp\logs" -Force
icacls "C:\publish\YourApp\logs" /grant "IIS AppPool\YourAppPool:(OI)(CI)M"

Restart IIS (iisreset), reload the page, then read logs\stdout_*.log. The actual exception will be there. Disable stdout logging after diagnosis — it slows the app and the log files grow without rotation.

You can also check the Windows Event Log:

powershellGet-EventLog -LogName Application -Source "IIS AspNetCore Module*" -Newest 10 | Format-List TimeGenerated, Message

HTTP Error 500.19 — Internal Server Error / web.config error

Usually means IIS can't read or interpret web.config. Causes:

  • Missing ANCM module. Confirm with Get-WebGlobalModule -Name "AspNetCoreModuleV2". If empty, repair the Hosting Bundle install.
  • Corrupt web.config. Validate XML syntax (open in any editor — broken XML is obvious). Re-publish from your build output to regenerate.
  • Wrong .NET version targeted. dotnet --list-runtimes doesn't include your target framework. Install the right runtime version.

HTTP Error 502.5 — Process Failure

The Kestrel process couldn't start. Same diagnosis as 500.30 — enable stdout logging, check Event Log. Common causes: missing dependency DLL, wrong appsettings.json for the environment, missing connection string.

This error code is associated with older outofprocess hosting model deployments — modern inprocess apps (the default since .NET 3.0) typically show 500.30 instead.

App works on http://localhost from the server but not externally

Almost always Windows Firewall. Run the New-NetFirewallRule commands from Step 6.

If the firewall rules exist but it still doesn't work, check that the Raff Server's network interface itself isn't blocking inbound 80/443. The Raff platform doesn't restrict inbound HTTP/HTTPS by default, but cloud providers in general sometimes do.

Bindings conflict — "Cannot create site because port 80 is in use"

Either the default IIS site is still bound to port 80, or another site already has the same binding. Solutions:

  • Stop or remove the conflicting site: Stop-Website "Default Web Site" then Remove-Website "Default Web Site"
  • Use a host header so multiple sites share port 80 by hostname:
    powershellNew-Website -Name "App1" -PhysicalPath "C:\publish\App1" -ApplicationPool "App1Pool" -Port 80 -HostHeader "app1.example.com"
    New-Website -Name "App2" -PhysicalPath "C:\publish\App2" -ApplicationPool "App2Pool" -Port 80 -HostHeader "app2.example.com"
    
    Each app needs its own DNS record pointing to the Raff Server.
  • Use different ports if you don't have a domain or are testing locally.

Hosting multiple ASP.NET Core sites on one Raff Server

The 4 vCPU / 8 GB Production plan comfortably handles 5-10 small ASP.NET Core sites running side-by-side. Pattern:

  1. One app pool per site — each site gets its own pool, which means its own w3wp.exe worker process. If one site crashes or hangs, others keep running.
  2. Each app pool: No Managed CodeSet-ItemProperty ... -Name "managedRuntimeVersion" -Value "" for every pool.
  3. Different host headers or ports per site — most Raff customers use host headers + Cloudflare for DNS.
  4. Memory limits per pool — set recycling.periodicRestart.privateMemory on each pool so a memory-leaking app can't take down the whole server. Example: limit each pool to 2 GB.
powershell# Limit RaffSampleAppPool to 2 GB private memory before recycle
Set-ItemProperty -Path "IIS:\AppPools\RaffSampleAppPool" `
    -Name "recycling.periodicRestart.privateMemory" `
    -Value 2097152  # in KB = 2 GB

Production-ready multi-site deployment is a topic deep enough for its own article. The single-site pattern in this guide is the foundation.

Verify deployment with PowerShell

After every deployment, run a quick smoke test to confirm IIS is healthy and your app is responding. Save this as C:\IIS\Verify-Site.ps1:

powershellparam([string]$SiteName = "RaffSampleApp", [string]$Url = "http://localhost")

Write-Host "`n=== IIS Site Health Check: $SiteName ===" -ForegroundColor Cyan

# IIS services
$services = Get-Service -Name W3SVC, WAS
$services | ForEach-Object {
    if ($_.Status -eq 'Running') {
        Write-Host "PASS: $($_.Name) running" -ForegroundColor Green
    } else {
        Write-Host "FAIL: $($_.Name) is $($_.Status)" -ForegroundColor Red
    }
}

# Site state
$site = Get-Website -Name $SiteName -ErrorAction SilentlyContinue
if (-not $site) {
    Write-Host "FAIL: site '$SiteName' not found" -ForegroundColor Red
    return
}
if ($site.State -eq 'Started') {
    Write-Host "PASS: site '$SiteName' is Started" -ForegroundColor Green
} else {
    Write-Host "FAIL: site '$SiteName' is $($site.State)" -ForegroundColor Red
}

# App pool state
$pool = Get-IISAppPool -Name $site.applicationPool -ErrorAction SilentlyContinue
if ($pool.State -eq 'Started') {
    Write-Host "PASS: app pool '$($pool.Name)' is Started" -ForegroundColor Green
} else {
    Write-Host "FAIL: app pool '$($pool.Name)' is $($pool.State)" -ForegroundColor Red
}

# No Managed Code check
if ($pool.ManagedRuntimeVersion -eq '') {
    Write-Host "PASS: app pool managedRuntimeVersion is empty (No Managed Code, correct for ASP.NET Core)" -ForegroundColor Green
} else {
    Write-Host "FAIL: app pool managedRuntimeVersion is '$($pool.ManagedRuntimeVersion)' (should be empty for ASP.NET Core)" -ForegroundColor Red
}

# HTTP test
try {
    $response = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop
    if ($response.StatusCode -eq 200) {
        Write-Host "PASS: $Url responds with HTTP 200 OK" -ForegroundColor Green
    } else {
        Write-Host "WARN: $Url responds with HTTP $($response.StatusCode)" -ForegroundColor Yellow
    }
} catch {
    Write-Host "FAIL: $Url is not responding — $($_.Exception.Message)" -ForegroundColor Red
}

# Recent error events (last 1 hour)
$events = Get-EventLog -LogName Application -Source "IIS AspNetCore Module*" -After (Get-Date).AddHours(-1) -ErrorAction SilentlyContinue
if ($events) {
    Write-Host "WARN: $($events.Count) ANCM event(s) in last hour:" -ForegroundColor Yellow
    $events | Select-Object -First 3 | Format-List TimeGenerated, EntryType, Message
} else {
    Write-Host "PASS: no recent ANCM errors" -ForegroundColor Green
}

Write-Host "`n=== Health check complete ===" -ForegroundColor Cyan

Run after every deployment:

powershell.\Verify-Site.ps1 -SiteName "RaffSampleApp" -Url "http://localhost"

Every check should show PASS. Any FAIL or WARN is something to investigate before declaring the deployment done.

Tested on Raff

Tested on: ASP.NET Core 10 Razor Pages app on IIS 10 — Microsoft .NET 10 Hosting Bundle 10.0.7, Microsoft .NET SDK 10.0.203 — on a Raff Windows Server 2025 — Production plan, 4 vCPU / 8 GB RAM / 120 GB NVMe, Windows Server 2025 Standard build 26100, Vint Hill, Virginia datacenter — on April 30, 2026, by Serdar Tekin. We verified end-to-end on a fresh VM: IIS install via Install-WindowsFeature with 19 features (no reboot required); .NET 10 Hosting Bundle install via winget (auto-registered AspNetCoreModuleV2 with IIS); .NET 10 SDK install via winget (separate package, required only for build/publish); dotnet new webapp --framework net10.0 Razor Pages template; framework-dependent publish to C:\publish\RaffSampleApp with auto-generated web.config using processPath=".\RaffSampleApp.exe" and hostingModel="inprocess"; default site removal + replacement with RaffSampleApp site on port 80; app pool RaffSampleAppPool with managedRuntimeVersion="" (No Managed Code); app pool identity granted RX permissions on the published folder via icacls; HTTP 200 OK confirmed via both Invoke-WebRequest and Edge browser load of the Razor Pages welcome page. Total time from blank VM to working app on IIS: under 10 minutes of command-execution time. Common-error scenarios documented from Microsoft Learn references rather than live-tested in this session.

What's next

Sources


Microsoft, Windows Server, IIS, .NET, ASP.NET Core, and Edge are trademarks of Microsoft Corporation. Raff Technologies is an independent infrastructure provider and is not affiliated with, sponsored by, or endorsed by Microsoft Corporation.

Published April 30, 2026 · Last updated April 30, 2026