Top Ten IIS Configuration Tips

1

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

IIS7-URL-Rewrite
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” />
<machineKey
validationKey=”21F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B44EA7DD4374267A75D7
AD972A119482D15A4127461DB1DC347C1A63AE5F1CCFAACFF1B72A7F0A281B”
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 www.iis.net 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
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.

www.iis.net 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"	 	 
$serverManager.CommitChanges()	 	 
$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;
}


My New Stories

March 2016 Web Hosting Deals
Powershell AD Group Management
Troubleshooting 403