IIS 7 Returns 304 Not Modified When 200 OK Expected: Kernel Cache and Static Content Caching Issues


2 views

I recently encountered a puzzling scenario where IIS 7 was returning 304 Not Modified responses even when:

  • No conditional headers (If-Modified-Since/If-None-Match) were present in the request
  • The client explicitly sent Cache-Control: no-cache
  • The resource wasn't cached on the client side

The issue stems from IIS 7's kernel cache (http.sys) behavior which can independently serve 304 responses without checking upstream caches. Key factors:

  1. Kernel caching is enabled by default for static content
  2. ETag validation occurs at kernel level
  3. The cache honors server-side expiration settings over client directives

After extensive testing, here's the configuration that resolves this:

<configuration>
  <system.webServer>
    <caching enabled="true" enableKernelCache="true">
      <profiles>
        <add extension=".js" policy="DisableCache" kernelCachePolicy="DisableCache" />
        <add extension=".css" policy="DisableCache" kernelCachePolicy="DisableCache" />
      </profiles>
    </caching>
    <staticContent>
      <clientCache cacheControlMode="DisableCache" />
    </staticContent>
  </system.webServer>
</configuration>

For more granular control, consider these additional methods:

// Programmatic solution in Global.asax
protected void Application_BeginRequest()
{
    if (Request.Url.AbsolutePath.EndsWith(".js") || 
        Request.Url.AbsolutePath.EndsWith(".css"))
    {
        Response.Cache.SetCacheability(HttpCacheability.NoCache);
        Response.Cache.SetNoStore();
    }
}
  • Kernel cache changes require IIS reset
  • Test with Fiddler/Postman to verify headers
  • Monitor performance impact when disabling caching
  • Combine with proper ETag configuration if needed

When IIS 7 unexpectedly returns HTTP 304 (Not Modified) responses for static resources (JS, CSS, images) even when the client sends Cache-Control: no-cache, this typically indicates kernel-level caching interference. The behavior persists because:

  • IIS kernel cache (http.sys) may still serve cached responses
  • ETag validations occur before client cache directives are processed
  • Default caching profiles override certain client headers

IIS 7's kernel caching operates at a lower level than application caching. To verify if this is the culprit, run:

netsh http show cachestate

If you see your static files listed, the kernel cache is serving them regardless of client headers.

This web.config solution properly disables caching at all levels:

<configuration>
  <system.webServer>
    <caching enabled="true" enableKernelCache="false">
      <profiles>
        <add extension=".js" policy="DisableCache" kernelCachePolicy="DisableCache" />
        <add extension=".css" policy="DisableCache" kernelCachePolicy="DisableCache" />
        <add extension=".png" policy="DisableCache" kernelCachePolicy="DisableCache" />
      </profiles>
    </caching>
    
    <staticContent>
      <clientCache cacheControlMode="DisableCache" />
      <remove fileExtension=".js" />
      <mimeMap fileExtension=".js" mimeType="application/javascript" />
    </staticContent>
  </system.webServer>
</configuration>

For stubborn cases, combine these approaches:

  1. Disable dynamic content compression temporarily
  2. Clear the kernel cache: net stop http /y followed by net start http
  3. Add unique query strings to resource URLs during development

For dynamic handling in ASP.NET applications:

protected void Application_PreSendRequestHeaders()
{
    if (Response.ContentType == "application/javascript" || 
        Response.ContentType == "text/css")
    {
        Response.Cache.SetNoStore();
        Response.Cache.SetCacheability(HttpCacheability.NoCache);
        Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
    }
}