Optimizing Cold Start Performance: Solving Slow Initial Requests in ASP.NET MVC on IIS 8.5/Windows Server 2012 R2


3 views

When hosting ASP.NET MVC applications on IIS, many developers encounter the notorious "cold start" delay where the first request after application inactivity takes significantly longer (1.5s in your case) compared to subsequent requests (45ms). This occurs even with proper application pool and preload settings.

The delay typically stems from multiple initialization processes:

  • JIT compilation of application code
  • Entity Framework model compilation (if used)
  • View compilation and caching
  • IIS worker process warm-up
  • CLR initialization overhead

1. Application Initialization Module Configuration

While you've tried basic settings, we need deeper configuration in applicationHost.config:

<system.applicationHost>
    <applicationPools>
        <add name="MyAppPool" startMode="AlwaysRunning">
            <recycling logEventOnRecycle="Time, Requests">
                <periodicRestart time="00:00:00" />
            </recycling>
            <processModel idleTimeout="0" />
        </add>
    </applicationPools>
    <sites>
        <site name="MySite" serverAutoStart="true">
            <application path="/" applicationPool="MyAppPool" preloadEnabled="true">
                <virtualDirectory path="/" physicalPath="C:\sites\MyApp" />
            </application>
        </site>
    </sites>
    <applicationInitialization remapManagedRequestsTo="warmingup.htm" skipManagedModules="false">
        <add initializationPage="/" />
    </applicationInitialization>
</system.applicationHost>

2. Precompiling Views

Add this to your Global.asax.cs to precompile Razor views:

protected void Application_Start()
{
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new PrecompiledMvcEngine(typeof(Startup).Assembly));
    AreaRegistration.RegisterAllAreas();
    // Other startup code...
}

3. NGEN Optimization for Your Assemblies

Create a post-build event in your project:

if "$(ConfigurationName)" == "Release" (
    "%windir%\Microsoft.NET\Framework64\v4.0.30319\ngen.exe" install "$(TargetPath)"
    "%windir%\Microsoft.NET\Framework64\v4.0.30319\ngen.exe" update /force
)

4. Warm-Up Module Implementation

Create a custom warm-up module (WarmUpModule.cs):

public class WarmUpModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += (sender, e) => 
        {
            var app = (HttpApplication)sender;
            if (app.Request.Url.PathAndQuery.Contains("warmup"))
            {
                // Preload critical paths
                var paths = new[] { "/", "/api/values", "/home/index" };
                foreach (var path in paths)
                {
                    using (var client = new WebClient())
                    {
                        client.DownloadData(app.Request.Url.GetLeftPart(UriPartial.Authority) + path);
                    }
                }
                app.Response.Write("Warm-up complete");
                app.Response.End();
            }
        };
    }
    
    public void Dispose() { }
}

For enterprise deployments, consider:

  • Implementing ARR (Application Request Routing) for continuous ping
  • Configuring Windows Task Scheduler to periodically hit health-check endpoints
  • Upgrading to Windows Server 2016+ which has improved IIS warm-up behavior
  • Implementing a custom ServiceControlManager to keep worker processes alive

To verify your improvements, add this telemetry to Global.asax:

protected void Application_BeginRequest()
{
    if (HttpContext.Current.Request.Headers["X-Warmup"] != null)
    {
        HttpContext.Current.Items["IsWarmupRequest"] = true;
    }
}

protected void Application_EndRequest()
{
    if (HttpContext.Current.Items["IsWarmupRequest"] != null)
    {
        return;
    }
    var duration = DateTime.Now - HttpContext.Current.Timestamp;
    Debug.WriteLine($"Request duration: {duration.TotalMilliseconds}ms");
}
  1. Verify Application Initialization module is installed (Server Manager > Add Roles)
  2. Confirm worker process stays active (check IIS Manager > Worker Processes)
  3. Test with empty cache and hard reload (Ctrl+F5) after deployment
  4. Monitor memory usage - 1GB might be tight for some applications

On my Windows Server 2012 R2 VPS running IIS 8.5, I noticed consistent 1.5-second delays for the first request after inactivity periods, despite having configured Start Mode=AlwaysRunning and Preload Enabled=True. Subsequent requests would process in just 45ms, revealing a classic application warm-up problem.

The standard IIS configurations only solve part of the problem. Here's what actually makes the difference:

// In Global.asax.cs
protected void Application_Start() 
{
    // Force initialization of critical components
    var warmupController = new HomeController();
    warmupController.Index(); 
    
    // Precompile razor views
    System.Web.Compilation.BuildManager.GetCompiledAssembly("~/Views/Home/Index.cshtml");
    
    // Initialize commonly used services
    MyDependencyResolver.Initialize(); 
}

These registry edits significantly improve warm-up behavior:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3SVC\Parameters]
"StartupTimeLimit"=dword:0000ea60

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ASP.NET\4.0.30319.0]
"Compilation.IdleTimeout"=dword:0000ffff

Create a PowerShell script to maintain application warmth:

$siteUrl = "http://localhost/healthcheck"
$interval = 300 # 5 minutes

while ($true) {
    try {
        Invoke-WebRequest -Uri $siteUrl -UseBasicParsing | Out-Null
    } catch {
        Write-Host "Warm-up request failed: $_"
    }
    Start-Sleep -Seconds $interval
}

With only 1GB RAM, these web.config settings help:

<system.web>
  <compilation optimizeCompilations="true" />
  <httpRuntime enableVersionHeader="false" 
               fcnMode="Disabled"
               maxRequestLength="4096"
               enable="true"
               executionTimeout="110" />
</system.web>

<system.webServer>
  <applicationInitialization 
    remapManagedRequestsTo="Startup.htm" 
    skipManagedModules="false">
    <add initializationPage="/healthcheck" />
  </applicationInitialization>
</system.webServer>

After implementing these changes:

  • First request latency reduced from 1500ms to ~300ms
  • No more complete application restarts during low traffic
  • Memory usage became more consistent