IIS Configuration can be as much an art as it is a science. Here are the Top 10 IIS Configuration Tips I use when building or maintaining an environments.

10. Choose the Right Hardware and OS from the Start

  • Virtualize where possible. Either running from a Cloud provider or locally on VMware or Hyper-V. A virtualized server gives you more flexibility to upgrade, clone, or recover your server later.
  • Upgrading an IIS Server OS is unrealistic, so choose the newest OS possible to extend the life of that server. As of today, go with Windows Server 2012 R2. If you must run Server 2008, use Windows Server 2008 R2.
  • Install as much memory as you can afford and your platform will accommodate. Today’s ASP.NET applications are very memory hungry, depending on how many applications you are hosting, you may need a lot.
  • Create three logical volumes, C:\ (System Volume), E:\ (Application Volume), F:\ (Logs Volume). On virtual hardware, different drives will not impact performance much. But by logically separating the data you prevent log files from filling up the Systems volume, easier file maintenance, and easier future upgrades.

9. Use URL Rewrite 2.0

URL Rewrite 2.0 in IIS 7.5

URL Rewrite 2.0 is a nice swiss army knife tool for IIS. It lets you tweak Request and Response headers, rewrite URLs for SEO friendly addresses, and inject Google Analytics Tracking Code for all content on your server.  URL Rewrite works with IIS 7, IIS 8, and IIS 10.

One thing to note if your using a Shared Configuration, you should install URL Rewrite first, then enable Shared Configuration. If its already installed, disable and revert your current config into the InetSrv directory, install URL Rewrite then Export and Re-enable Shared Configuration.

8. Use the Right Load Balancer Keep Alive

Good IIS Configuration isn’t strictly limited to what happens in inetinfo.exe. IIS exists within a network ecosystem, and the load balancer is a key part of that. If you are going to be putting IIS behind a load balancer, one often over looked step is configuring the right keep alive. The default health monitor on most load balancers is either a ping check or port check. If your application or app pool stops responding on one server, it will still accept pings or port 80 connections. You need a better health check.

  • Try and setup a dedicated ASPX resource that returns a simple and small response. Parse for that response in your keep alive.
  • Make sure the ASPX resource isn’t performing any database or web service queuries that will put undo stress on your infrastructure.
  • Make sure your keep alive resource is running in the same App Pool as your primary application.
  • Make sure your keep alive resource is running in the same website as your primary website, with the same binding.

Read the F5 Load Balancing 101 white paper, for more in depth discussions of keep alives and health monitors.

7. Clean up Your HTTP Headers

Tweaking your HTTP Headers is very easy in IIS 7 and 8 so there’s no reason not to do it. First remove the ASP.NET header, by doing so you’ll shave bytes of each response and if you run any penetrations tests it will likely be flagged. Second, add a Max Age Header to ensure clients are caching content and cut down on server trips. Also note that ETags headers do not need to be set in IIS 7 and 8. Third, remove the EnableVersionHeader from your machine.config, more on that in #6.

Remove X-Powered-By
appcmd set config /section:httpProtocol /-customHeaders.[name='X-Powered-By'] 

Add CacheControlHeader
appcmd set config /section:httpProtocol /+customHeaders.[name='cacheControlHeader',value='max-age=604800']

List Headers
appcmd list config /section:httpProtocol

6. Update Machine.config

IIS Configuration Tips
machine.config recommendation

If you are running a Web Farm you need update the machine.config on each server with a manually set machineKey value. There’s two easy ways to generate keys, open the MachineKey Feature in the IIS 7 Manager and click Generate Keys. The other option is to use one of the many online MachineKey Generators.

As mentioned above, disable the EnableVersionHeader to shave bytes off your response and reduce your attack surface.

Additionally, especially in Production or Pre-Production environments you should set the deployment retail=”true”. This will override debug=”true” in a web.config for the most part, but you still should ensure applications are compiled as debug=”false”.

In .NET 1.1 and 2.0 there was a lot of discussion and debate about tuning the ProcessModel settings. Adjusting Thread Counts and MaxConnections. What I’ve seen from Microsoft recently is you should run with the defaults unless you have some very specific needs to tweak this. My feeling is that 90% of applications will run best under the default settings. But keep these settings in the back of your head.

<deployment retail=”true” />
<httpRuntime enableVersionHeader=”false” />
decryptionKey=”ABAA84D7EC4BB56D75D217CECFFB9628809BDB8BF91CFCD64568A145BE59719F” validation=”SHA1″ />
Your machine.config should look like this.

5. Log Recycle Events

Whenever an Application Pool recycles you want to know about it, or at the very least be able to confirm it recycled. By Default IIS only logs in the Event Log for a select number of causes of recycles. Change the default config so all recycles are logged.

Import-Module WebAdministration	 	 
Set-WebConfigurationProperty '/system.applicationHost/applicationPools/applicationPoolDefaults/recycling' -Name logEventOnRecycle -value &quot;Time, Requests, Schedule, Memory, IsapiUnhealthy, OnDemand, ConfigChange, PrivateMemory&quot;	 	 
$pools = Get-ChildItem iis:\apppools	 	 
foreach ($pool in $pools){	 	 
$poolname = $pool.Name	 	 
Set-ItemProperty iis:\apppools\$poolname -name recycling -value @{logEventOnRecycle=&quot;Time, Requests, Schedule, Memory, IsapiUnhealthy, OnDemand, ConfigChange, PrivateMemory&quot;}	 	 
Change All App Pools to Log Recycle Events

4. Warm up Your Application

What does Application Warm Up mean? Out of the box IIS will shut down an app pool after 20 minutes of inactivity. So the first customer to hit your app will be cold starting the application. The best case scenario it means launching the w3wp.exe process and spinning up threads for handling HTTP requests if you have just static content. If you have an ASP.NET application, you have to load the application into memory, execute Global.asax events, and establish database connections. The worst case scenario, your application performs expensive caching of the database into memory. In that scenario your application may be unresponsive for a few minutes.

  • In IIS7 or IIS8, make sure you disable the Process Model IdleTimeout for your app pools. Unless you have lots of app pools and heavy memory pressure, you should always set this to 0.
  • The Application Initialization Module introduces functionality for IIS 7.5 to automatically launch an ASP.NET application when IIS starts. You’re not able to use the IIS Manager GUI to manage this in IIS 7.5, so you’ll have to configure it directly in the applicationHost.config. There’s a pretty concise MSDN blog post outlining how to setup IIS7.
  • IIS8 introduces Application Initialization natively. This article on provides a very thorough walk through on how to setup App Init.
    IIS8 Preload
    IIS 8.5 Preload
  • Other options are configuring your load balancer health monitor to keep the application warmed up. Or using an external URL Monitor to constantly hit the application on each server.

3. Caching and Compression

IIS8 Compression Feature

IIS7 and IIS8 have two primary features to enhance performance. Output Caching, which caches the response in memory. And Compression which compresses the response before sending it across the wire.

I don’t find Output Caching to be that useful. It basically allows you to define cache rules for Dynamic Pages that don’t change very often. That seems like a problem waiting to happen, for a small performance boost. So I would skip this for Dynamic Content. However, for Static Content, there’s not reason not to use it. Static Content is Cached by default, but I like to tweak the config so static responses get cached on the first hit and stay in cache longer. This is done by  creating a registry entry and updating the ServerRuntime config in IIS. See the Powershell below for how to tweak your server.

netsh http show cachestate	 	 
Show Current Cache Contents

HTTP Compression is a really great feature to enhance performance . Especially important for mobile clients on 3G networks, this feature sends a compressed version of the response across the network. Text based content is compressed by default. But you should also enable for use with Dyanamic Content, specifically JSON and XML content types. The following Powershell enable Compression and adds the content type rules.

# Enable a 10 minute Cache TTL	 	 
New-ItemProperty -Path HKLM:\System\CurrentControlSet\services\InetInfo\Parameters -Name ObjectCacheTTL -PropertyType DWORD -Value 600	 	 
New-ItemProperty -Path HKLM:\System\CurrentControlSet\services\InetInfo\Parameters -Name OutputCacheTTL -PropertyType DWORD -Value 600	 	 
# Cache a Response after one hit every 10 minutes	 	 
Set-WebConfigurationProperty &quot;/system.webServer/serverRuntime&quot; -pspath IIS:\ -name frequentHitThreshold -value 1	 	 
Set-WebConfigurationProperty &quot;/system.webServer/serverRuntime&quot; -pspath IIS:\ -name frequentHitTimePeriod -value &quot;00:10:00&quot;	 	 
# Enable Static and Dynamic Compression, include XML and JSON responses	 	 
Set-WebConfigurationProperty &quot;/system.webServer/urlCompression&quot; -pspath IIS:\ -name doStaticCompression -value &quot;true&quot;	 	 
Set-WebConfigurationProperty &quot;/system.webServer/urlCompression&quot; -pspath IIS:\ -name doDynamicCompression -value &quot;true&quot;	 	 
Add-webconfiguration &quot;/system.webServer/httpCompression/dynamicTypes&quot; -pspath IIS:\ -value (@{mimeType=&quot;application/xml&quot;;enabled=&quot;true&quot;})	 	 
Add-webconfiguration &quot;/system.webServer/httpCompression/dynamicTypes&quot; -pspath IIS:\ -value (@{mimeType=&quot;application/json&quot;;enabled=&quot;true&quot;})	 	 
Tweak Caching and Compression Configuration

2. Use a Shared Configuration

IIS7 Shared Config
IIS7 Shared Config

One of the major features introduced in IIS7 was the Shared Configuration. This feature makes the applicationHost.config copyable between servers as long as you have the same Encryption Keys present. This provides a few benefits:

  • You can configure a farm of servers to point to a central applicationHost.config on a file share.
  • Even if you’re not using a central applicationHost.config, you can xcopy the config file between servers in the same farm.
  • You can create a boilerplate applicationHost.config that contains your base customizations and use as part of new server builds. contains a very detailed article on Configuring Shared Configuration, so I won’t rehash it. But I will include a Powershell DSC Script resource for enabling Shared Config. If you’re having problems setting up a Shared Configuration, review my article on Troubleshooting IIS Shared Configuration.

Script EnableSharedConfig	 	 
GetScript = {	 	 
$c = Get-Content "C:\State\EnableSharedConfig.state"	 	 
return @{ State = ($c) }	 	 
TestScript = {	 	 
[System.Reflection.Assembly]::LoadFrom( "C:\windows\system32\inetsrv\Microsoft.Web.Administration.dll" )	 	 
$serverManager = New-Object Microsoft.Web.Administration.ServerManager	 	 
$config = $serverManager.GetRedirectionConfiguration()	 	 
$redirectionSection = $config.GetSection("configurationRedirection")	 	 
$r = $redirectionSection.Attributes["enabled"].Value	 	 
if ($r -eq "true"){return $true } else { return $false }	 	 
SetScript = {	 	 
[System.Reflection.Assembly]::LoadFrom( "C:\windows\system32\inetsrv\Microsoft.Web.Administration.dll" )	 	 
$serverManager = New-Object Microsoft.Web.Administration.ServerManager	 	 
$config = $serverManager.GetRedirectionConfiguration()	 	 
$redirectionSection = $config.GetSection("configurationRedirection")	 	 
$redirectionSection.Attributes["enabled"].Value = "true"	 	 
$redirectionSection.Attributes["path"].Value = "E:\Config\IIS"	 	 
$d = Get-Date	 	 
Add-Content "C:\Platform\State\EnableSharedConfig.state" "@ $d"	 	 
DSC Script Resource to Enable Shared Config

1. Control Application Pool Recycling

IIS7 Recycling
IIS7 Recycling Options

In my opinion the biggest source of problems in new IIS configurations is Application Pool Recycling. This is the one area where the default IIS settings are problematic for most applications. As a general rule of thumb, you want to minimize the number of Recycles that occur, and when they do occur it should be well controlled. Here’s what you need to look at:

  • Application Pool Idle Timeout – The default is 20 minutes, if your app gets no requests in 20 minutes then the App Pool will shut down. Change this 0.
  • Regular Time Interval – The default is 1740 minutes, every 29 hours the App Pool will recycle, regardless of activity. You don’t want that happening, set this to 0.
  • Specific Times – By default no schedule is set, I would recommend setting an off peak time to recycle like 4AM.
  • Most importantly, because I constantly see developers and administrators doing this. Stop using IISReset!! A lot of people cling to this idea from IIS 5, and it only destabilizes your environment. Unless your installing ISAPI Filters or other System level change, you don’t need an IISReset. Recycle the App Pool in question if its needed, but do not restart IIS.
Updating Application Pools in Powershell
Import-Module WebAdministration -ErrorAction SilentlyContinue

$pools = Get-ChildItem iis:\apppools

foreach ($pool in $pools){
	$poolname = $pool.Name
	Set-ItemProperty IIS:\AppPools\$poolname -name processModel -value @{idletimeout=&amp;quot;0&amp;quot;}
	set-ItemProperty IIS:\AppPools\$poolname -Name Recycling.periodicRestart.schedule -Value @{value=&amp;quot;04:00:00&amp;quot;}
	set-ItemProperty IIS:\AppPools\$poolname -Name Recycling.periodicRestart -Value @{time=&amp;quot;00:00:00&amp;quot;}
    Set-ItemProperty IIS:\AppPools\$poolname -name Recycling -value @{logEventOnRecycle=&amp;quot;Time, Requests, Schedule, Memory, IsapiUnhealthy, OnDemand, ConfigChange, PrivateMemory&amp;quot;}

	Write-Host &amp;quot;Updated $poolname settings&amp;quot;

Tags : configurationfeaturediispowershell
Byron Pate

The author Byron Pate

  • Pingback: Quick IIS App Pool Recycle |

  • Pingback: Script IIS Virtual Directory Setup |

  • Why do you believe using IISReset destabilizes your environment?

  • Carl, I don’t think IISReset does any long term damage to your environment. But if you want to restart 1 application and you have 10 applications hosted on the same environment. If you do an IISReset you just needlessly restarted those other applications. And the users or testers of those 10 applications may have gotten errors or seen slow performance as a result. If you just restarted the Application Pool of the 1 application, no impact to your other applications. But if your just doing an IISReset on a Sandbox environment, and your the only person using it then its less of a concern.

  • Eric Miller

    Byron, your DSC script doesn’t seem to address the username/password/and Encryption key that are required for adding shared configuration on an IIS box. Is there a way in powershell (or another method) to simply enable shared config, completing all of the GUI steps (enable, \\UNC, username, password, then crypto key)?

    thanks…great article.

  • Eric, that’s a valid question. For specifying an account to access the shared config on a UNC, its actually pretty simple. The Powershell would look like this. You just have to set the properties for Username and Password.

    [System.Reflection.Assembly]::LoadFrom( “C:\windows\system32\inetsrv\Microsoft.Web.Administration.dll” )
    $serverManager = New-Object Microsoft.Web.Administration.ServerManager
    $config = $serverManager.GetRedirectionConfiguration()
    $redirectionSection = $config.GetSection(“configurationRedirection”)
    $redirectionSection.Attributes[“enabled”].Value = “true”
    $redirectionSection.Attributes[“path”].Value = “\\server\share\Config\IIS”
    $redirectionSection.Attributes[“userName”].Value = “uncuser”;
    $redirectionSection.Attributes[“password”].Value = “uncpassword”;

    For Encryption Keys, its a bit more involved. If you’re going to share the config across multiple servers you need to sync your Machine Keys, if you have any encrypted passwords in the appHost.config. The most common scenario is using a domain account for your App Pool Identity. But according to IIS Documentation you only have to set Shared Config Crypto Key when using the UI, and that Key is only used with the UI, see the quote below. I’ve never used this exact scenario before, but I’m going to test it out and post the results. Stay tuned…

    “When setting up configuration redirection, the exported files are expected to have been exported by using the UI. This is because the UI exports and imports items such as the password-protected encryption keys by using its own custom format.”

  • Eric Miller

    thanks Byron, that PS looks like it will work (testing now).

    re: “For Encryption Keys, its a bit more involved. If you’re going to share the config across multiple servers you need to sync your Machine Keys, if you have any encrypted passwords in the appHost.config. ”

    I’m pretty sure we *don’t* have encrypted passwords in the appHost.config, but when I enable shared config using the GUI, I get asked for the shared key every time (right after clicking “apply” on the enable\unc\user\pass dialogue). Two questions here:

    1. how do you recommend determining if you do/do not have encrypted passwords in the appHost.config?
    2. in cases where appHost.config does not have encrypted passwords, is it safe to assume one can skip syncing the machine keys when enabling shard config from a .ps1?

  • 1. I think 99% of the use cases for having encrypted passwords are if you have a custom App Pool Identity or you have a virtual directory on a UNC Share and use a Connect As account. The easy way to test, open applicationHost.config and search for “[enc:”

    2. If you don’t have passwords encrypted in your appHost.config you don’t technically need to sync the machine keys. I’ve run a web farm with that setup and didn’t have any issues. But one warning, if later you do have to use a domain account and have to sync up key and then reexport appHost.config it can be a real pain.

  • Pingback: Troubleshooting IIS Shared Configuration -

  • Pingback: Script IIS Shared Configuration -

  • Matt Waddell

    Thank you – this is a really helpful article, well explained.

  • Joakim

    Thanks for a great post!

    Do you have any best-practices for how to ensure debug is not enabled in any of the applications on a server?

  • That’s a topic that has been debated for years. I know a lot of deployment tools (Octopus Deploy, Buildmaster) can help by introducing checks into your build process.

    I’ve used a Powershell one-liner to scan a whole directory for debug=”true”. This will even show DLLs that have been compiled with debug=”true”

    get-childitem e:prodikadevweb -include *.* -rec | select-string -pattern “bdebug=(“”|”)(True|true|TRUE)(“”|”)”

  • Byron; We have quite a few Application Pools (23) running on our application server; if I switch the Idle Timeout to ‘0’ on all of them, is there a possibility that all these active App Pools will overtax the server? What are the resources that we should monitor to ensure this change doesn’t stress the server?

  • sanjay

    Hi Byron,

    I tried to see if it can detect the DLL’s compiled in debug mode but nothing shows up in the output. Could you help me figure out where I am going back as I ran exactly the same command you provided below.

    get-childitem e:Applications -include *.* -rec | select-string -pattern “bdebug=(“”|”)(True|true|TRUE)(“”|”)”


  • Deadly-Bagel

    I am a little wary of shared configuration, we were using it to configure IIS on servers hosting a dedicated web product for customers. As the config slowly changed over time we knew less and less about what it was actually doing and eventually I went to change something to find half the settings were corrupt on all the server’s we’d been installing over the last 6 months.

    Thus I would recommend using Shared Config only when actually sharing the config between servers. If you want to copy, set up a PowerShell script to create all the sites and set all the config options you need. It has the additional advantage of being able to be run remotely and/or unattended which I was never able to achieve with shared configuration.