The first request to an ASP.NET application after an IIS restart is always the slowest. Application pool recycling, a server reboot, or a config change forces the worker process to recompile assemblies and warm caches, and that first user pays the entire cost — often five to thirty seconds of spinner. Application Initialization, built into IIS 8 and later, lets the server do that work before it accepts traffic.
When this matters (and when it does not)
If your application recycles nightly, if you deploy frequently, or if you run more than one node behind a load balancer, warmup turns a user-visible freeze into a background task. For a single long-running app pool that recycles weekly, the payoff is smaller but the setup cost is near zero, so there is little reason to skip it.
Step 1: Install the feature and confirm the module
The role service is called Application Initialization. Install it in an elevated PowerShell session:
Install-WindowsFeature Web-AppInit
# verify it loaded
Get-WebGlobalModule | Where-Object Name -eq 'ApplicationInitializationModule'
If you manage IIS with the Configuration Editor rather than feature management, the module appears under system.webServer/applicationInitialization at the server level.
Step 2: Enable preload on the application and the pool
Two switches must both be set: the application must be marked for preload, and the pool must start automatically. Missing either one silently disables the whole mechanism.
Import-Module WebAdministration
Set-ItemProperty IIS:\Sites\MyApp -Name applicationDefaults.preloadEnabled -Value $true
Set-ItemProperty IIS:\AppPools\MyApp -Name startMode -Value AlwaysRunning
Set-ItemProperty IIS:\AppPools\MyApp -Name processModel.idleTimeout -Value "00:00:00"
Setting idleTimeout to zero stops IIS from unloading the worker process after twenty minutes of inactivity. Combined with AlwaysRunning, the app stays resident and warm between requests.
Step 3: Declare a warmup URL
Preload alone starts the process; it does not exercise the application. Add a web.config entry pointing at a lightweight endpoint that touches the expensive paths without doing real work.
<system.webServer>
<applicationInitialization doAppInitAfterRestart="true">
<add initializationPage="/warmup" />
<add initializationPage="/health/db" />
</applicationInitialization>
</system.webServer>
IIS will not route traffic to the site until those pages return a successful status. Design /warmup to prime the things that are actually slow: JIT-compile controller types, open the database connection pool, load configuration, and populate any in-memory cache.
| Warmup target | Why it helps | Cost |
|---|---|---|
| Razor/compiled views | First render is fast | Low |
| DB connection pool | First query does not pay handshake | Low |
| In-memory cache | Avoids cold-start stampede | Medium |
| External API clients | DNS and TLS warm | Medium |
Step 4: Do not slow down deployment
There is a tradeoff to respect: IIS now waits for your warmup pages before serving traffic, so a slow warmup becomes deployment latency. Keep warmup endpoints fast and idempotent, and cap their runtime at a few seconds. If warmup fails, IIS retries rather than serving a cold site — consider that a feature, but monitor the failed-request log so a broken warmup endpoint does not look like an outage.
Multi-node and load-balanced deployments
Warmup matters most when more than one node serves traffic. Without it, a rolling deployment or a pool recycle on one node sends that node’s share of requests into a cold app while its peers are already warm — the imbalance shows up as a latency spike that looks like a network problem. With preload enabled on every node, the load balancer only needs a health probe that respects the same warmup endpoint.
| Deployment style | Cold-start exposure | Warmup value |
|---|---|---|
| Single node, weekly recycle | One window | Moderate |
| Rolling deploy, 2+ nodes | Per-node spike | High |
| Autoscaling instances | Every scale-out | Very high |
| Blue/green swap | On cutover | High |
Interaction with recycle settings
Application Initialization does not override the recycle schedule — it complements it. Leave a scheduled recycle in place for the memory-reclamation benefit, but pair it with overlapping recycle so a new worker process starts and warms before the old one retires. Check the pool’s Disable Overlapped Recycle setting is off:
Get-ItemProperty IIS:\AppPools\MyApp `
-Name recycling.disallowOverlappingRotation
# Value should be False; set it explicitly if it is not
Set-ItemProperty IIS:\AppPools\MyApp `
-Name recycling.disallowOverlappingRotation -Value $false
Overlapped recycle combined with a warmup page means users never touch a cold process, even during a scheduled recycle. This single pairing eliminates the most common “the site is slow every night at 3am” complaint.
Common pitfalls that silently break warmup
- Warmup page returns a redirect. IIS follows redirects for initialization pages in some configurations; a warmup URL that 302s to the login page may never warm the real code path. Return 200 directly.
- Preload enabled but pool start mode left on OnDemand. The process still stops, and the next request is cold again.
- Warmup page depends on an external service that is also cold. Chain the warmup so the DB ping happens before the cache fill, not in parallel.
- Application Initialization installed but web.config not deployed. The module does nothing without the
applicationInitializationsection. - Health probes configured to ignore warmup. A probe that returns healthy before warmup completes routes traffic to a cold process anyway.
Each of these produces the same symptom — a first request that is still slow — so work through the list in order when warmup appears not to work.
Verify it worked
- Recycle the pool:
Restart-WebAppPool -Name MyApp - Immediately time a request to a heavy page — it should already be warm.
- Check
%SystemDrive%\inetpub\logs\LogFilesfor the warmup hits before any user request. - Confirm the pool process is running with no request:
Get-Process w3wpshould show it resident.
Set up correctly, Application Initialization removes the cold-start penalty entirely and makes deployments invisible to users instead of a brief outage. It is one of the highest-value ten-minute IIS changes available, particularly on VPS deployments that lack the spare capacity to absorb a stampede of cold requests.


