IIS 403 Errors on Static Files After a Deployment

A web site returned HTTP 403 errors for static files after a deployment. The IIS Dynamic IP Restrictions feature caused the errors. This article shows the diagnosis and the fix.

The symptoms

The page loaded. Some script and style files loaded. Other script, style, and font files failed with HTTP 403.

These files loaded correctly in a new browser tab. A single fetch request returned 200. Only page loads failed.

The errors appeared after a pipeline deployment. Further deployments did not remove the errors.

The diagnosis

The login page requests about 25 static files at once. A browser with HTTP/2 sends all requests at the same time. A burst test proved the limit:

(async () => {
  const r = await Promise.all(Array.from({ length: 30 }, () =>
    fetch('/js/site.js', { cache: 'no-store' })
      .then(x => x.status).catch(() => 'ERR')));
  console.log(r.reduce((a, s) => (a[s] = (a[s] || 0) + 1, a), {}));
})();

The result:

{200: 10, 403: 20}

Ten requests passed. Twenty requests failed. The server denied requests above a concurrency limit.

The application code was clean. The same test on a local build returned 200 for all 30 requests. The application has no static file rate limiter.

The root cause

IIS Dynamic IP Restrictions limited concurrent requests. The limit applied at server level and site level. The limit was 10 concurrent requests.

The page needed more than 10 requests at once. IIS returned 403 for every request above the limit.

The configuration lived in applicationHost.config. The IIS Manager UI did not show the settings. This command displayed the settings:

C:\Windows\System32\inetsrv\appcmd.exe list config "CDISV2SCUATWU/" -section:system.webServer/security/dynamicIpSecurity
<dynamicIpSecurity>
  <denyByConcurrentRequests enabled="true" maxConcurrentRequests="10" />
  <denyByRequestRate enabled="true" />
</dynamicIpSecurity>

The fix

Open PowerShell as administrator. Run these commands:

# Site level
Set-WebConfigurationProperty -PSPath "MACHINE/WEBROOT/APPHOST" -Location "CDISV2SCUATWU" -Filter "system.webServer/security/dynamicIpSecurity/denyByConcurrentRequests" -Name "enabled" -Value "False"
Set-WebConfigurationProperty -PSPath "MACHINE/WEBROOT/APPHOST" -Location "CDISV2SCUATWU" -Filter "system.webServer/security/dynamicIpSecurity/denyByRequestRate" -Name "enabled" -Value "False"

# Server level
Set-WebConfigurationProperty -PSPath "MACHINE/WEBROOT/APPHOST" -Filter "system.webServer/security/dynamicIpSecurity/denyByConcurrentRequests" -Name "enabled" -Value "False"
Set-WebConfigurationProperty -PSPath "MACHINE/WEBROOT/APPHOST" -Filter "system.webServer/security/dynamicIpSecurity/denyByRequestRate" -Name "enabled" -Value "False"

Recycle the application pool:

C:\Windows\System32\inetsrv\appcmd.exe recycle apppool "CDISV2SCUATWU"

Reload the page. All assets loaded. The errors stopped.

If you keep the feature enabled, use generous limits. A modern page can send 100 requests at once. Set the concurrent limit to 200 or more.

Summary

  • Static files returned 403 after a deployment
  • IIS Dynamic IP Restrictions denied requests above 10 concurrent
  • The limit applied at server level and site level
  • Set denyByConcurrentRequests and denyByRequestRate to false
  • Recycle the application pool after the change

Posted

in

, , ,

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *