IIS Best Practices
Published Mar 20 2020 06:20 AM 116K Views
Microsoft

It has been almost eight years since I first wrote a blog on IIS best practices. During this time, several new versions of IIS have arrived, some reached end of lifecycle; we were introduced a new development platform called .NET Core; a new HTTP version… And after eight more years of experience on a variety of customers and environments, finally I thought it was time for an update. Hope you enjoy it :)

 

See here for the old version.

 

For a very long time, I have been asked for a document on IIS best practices. There are some blogs/articles on the Internet, but I could not find a complete one. Actually, the main problem here is that there cannot be “best practices” for a web server. A web server is just a hosting platform for applications, and, each and every application has its own needs. Therefore, in many cases, you will not have one universal best practice.

 

Having these said, I tried to gather a list of things one should check while configuring an IIS server (and an application on IIS). I should say that these are my own thoughts based on my own experience. It may be possible that you will find some resources mentioning just the opposite of what I say. For such cases, consider your applications specific needs and decide accordingly.

 

Most of the below mentioned settings or recommendations apply to the web applications hosted on cloud solutions (like Azure Web Apps) and some will already be in-place. Especially some of the performance best practices listed below would help you keep your cloud costs as low as possible.

 

One more thing before we get to my recommendations: Keep in mind that web servers are just hosting platforms for your applications. They can only be as secure and as performant as your code. Therefore, do not expect the below list to address and resolve all your problems.

 

Application pool configuration

  • Create one application pool for each application. You might consider grouping applications to pools if there are too many applications and/or sites (hundreds, thousands) hosted, for administration and management purposes only.
  • Use Integrated mode. If your application does not work in Integrated mode, use Classic mode until you resolve the issue.

Application Pools <applicationPools>

https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/#overview
 

  • Use 64bit application pools if you do not have a limitation by any of the modules your application uses. Even if you have such a limitation, try to resolve it.

enable32BitAppOnWin64

https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/applicati...
 

  • Use ApplicationPoolIdentity as identity

Application Pool Identities

http://learn.iis.net/page.aspx/624/application-pool-identities/

 

  • If your server is domain-joined and if your application needs to access resources over network, consider using "group managed service accounts" (gMSA):

Group Managed Service Accounts Overview

https://docs.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/group-manage...

 

What's New for Managed Service Accounts

https://docs.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/what-s-new-f...
 

  • Do not leave recycling configuration as default:
    • Idle Time-out (minutes):
      • Either set this to 0 (zero)
      • Or set "Idle time-out action" to "Suspend"
    • Regular time interval (minutes): 0 (set to zero)
    • Specific Times: Set to the least used time of the day:

Recycling Settings for an Application Pool <recycling>
https://docs.microsoft.com/en-us/iis/configuration/system.applicationHost/applicationPools/add/recyc...

If you are confident that your application does not have any resource leak issues and if your application pool is recycled during deployments every now and then, you might consider totally disabling recycling.
 

  • Set "logEventOnRecycle" for all reasons: Time, Requests, Schedule, Memory, IsapiUnhealthy, OnDemand, ConfigChange, and PrivateMemory:

logEventOnRecycle

https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/add/recyc...
 

  • Do not set any private memory limit unless troubleshooting specific scenarios. If you do set it, turn it off as soon as possible.
     
  • Do not ever set virtual memory limit. Reserved memory usage is meant with "virtual bytes" and especially in 64bit environments, processes reserve huge amounts of memory (that is not an actual reservation, but it is a different story).
     
  • Consider increasing "Maximum Worker Processes" (web gardening) number - with caution:
    • Make sure that your application is sessionless or uses "out-of-process" session state
    • Make sure that your application does not cache too much data (as it would be cached in each process which would then cause performance degradation)
       
  • Keep "Rapid-fail protection" enabled, but makes changes according to application pool settings, i.e. "Maximum Worker Processes"
    • Make sure that your load balancer, if there is one, and "Service Unavailable Response type" setting of your application pools are set accordingly.

Failure Settings for an Application Pool <failure>

https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/add/failu...
 

Logging

  • Do not ever turn logging off, and:
    • Keep your log files (IIS, HttpErr and any application log you might have) on a non-system disk/partition
    • Turn on as many fields as possible (at least defaults + sc-bytes and cs-bytes)

HTTP Logging <httpLogging>

https://docs.microsoft.com/en-us/iis/configuration/system.webserver/httplogging
 

  • Monitor disk usage as the logs might grow quite fast
    • Schedule scripts to archive older logs
       
  • Use "custom fields" if you need to log extra fields. If you have an NLB  device which NATs (causing the client IP to be hidden from IIS servers), add the real client IP as a custom field:
    • Do not use "advanced logging" module as it is a very old module and there may be some issues with it, like memory leaks.
    • If you are hosting service applications (web services or WCF) consider adding method names to headers (like SOAPAction header) and log them in IIS logs using custom fields.

Adding Custom Fields to a Log File for a Site <add>

https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/sites/site/logfile/customf...

 

  • IIS logs provide us with invaluable data on general health and performance of our applications. Periodically analyze them to have an idea on the performance baseline and trends.
    • Although the latest version was released in 2005, Log Parser is still a very powerful tool for log analysis

Log Parser 2.2

https://www.microsoft.com/en-us/download/details.aspx?id=24659
 

  • You can use Azure Monitor to collect and analyze IIS logs

Collect IIS logs in Azure Monitor

https://docs.microsoft.com/en-us/azure/azure-monitor/platform/data-sources-iis-logs
 

  • Also, keep an eye on HttpErr logs as they might contain some details on some specific problems:

Error logging in HTTP APIs

https://support.microsoft.com/en-us/help/820729/error-logging-in-http-apis

 

  • Configure Windows Error Reporting to collect full user dumps when worker processes (w3wp.exe) crash:
    • Note that WER might sometimes not be able to collect memory dumps of .NET applications, but for most scenarios, it would be best to have this setting

Collecting User-Mode Dumps

https://docs.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps

 

Content

  • Keep your content on a non-system disk, if possible, on a disk/partition other than the one you keep your logs.
     
  • Configure static and dynamic compression - if CPU usage and content types permit

HTTP Compression <httpCompression>
https://docs.microsoft.com/en-us/iis/configuration/system.webServer/httpCompression/
 

  • Configure antivirus applications to exclude at least:
    • Content folders
    • Temporary ASP.NET Files folder (C:\Windows\Framework[64]\vX.X.XXXXX\)
    • Any temp folder if used

Configure Windows Defender Antivirus exclusions on Windows Server

https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-antivirus/confi...
 

  • Use "Output caching" whenever possible (be careful doing this as it might cause users to access each other's sessions)

Caching <caching>

https://docs.microsoft.com/en-us/iis/configuration/system.webserver/caching/
 

  • Remove any unused "default document" setting and keep only the one you will use

Default Document <defaultDocument>

https://docs.microsoft.com/en-us/iis/configuration/system.webserver/defaultdocument/
 

Security

  • Use end-to-end encryption
    • If you have reverse proxy and/or load balancer in front of your web servers, prefer to use SSL-bridging instead of SSL-offloading
    • Disable older SSL/TLS versions than TLS 1.2
    • Disable weak cypher suits
    • SSL/TLS and cypher suit settings are server-wide settings, and IIS supports whatever the OS supports. However, for .NET applications check the below article:

Transport Layer Security (TLS) best practices with the .NET Framework

https://docs.microsoft.com/en-us/dotnet/framework/network-programming/tls

 

  • Add security headers to your applications:

Content Security Policy (CSP)

https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/store-policies/csp

 

HSTS Settings for a Web Site <hsts>

https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/sites/site/hsts

 

X-Frame-Options

https://tools.ietf.org/html/rfc7034

 

OWASP Secure Headers Project

https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#tab=Headers

 

  • Configure "Request Filtering":
    • “Allow unlisted file name extensions": Uncheck (allow only the extensions you will use; add "." to allow extensionless requests)
    • “Allow unlisted verbs": Uncheck (allow only the verbs you will use) 
    • Lower "request limits" if possible

Request Filtering <requestFiltering>

https://docs.microsoft.com/en-us/iis/configuration/system.webserver/security/requestfiltering/

 

  • Remove HTTP headers which identifies the server and application. These headers are believed to cause security vulnerability:

removeServerHeader

https://docs.microsoft.com/en-us/iis/configuration/system.webserver/security/requestfiltering/#new-i...

 

Remove Unwanted HTTP Response Headers

https://techcommunity.microsoft.com/t5/iis-support-blog/remove-unwanted-http-response-headers/ba-p/3...

 

  • Set NTFS permissions on the content folders as needed:
    • Do not give unnecessary permissions to unnecessary users. Remove permissions of Users and other groups. You should consider authentication and impersonation configurations to do this.
    • The content folder should only need "read" and "read and execute" permissions. If your application needs to write something (like logs or temp files) write them to a separate folder (one for each application on the server) and give "write" permission only to that specific folder.
    • Make sure that the folders with write permissions cannot be accessed through HTTP protocol. i.e. make sure that access to that folder is denied by Request Filtering module.

  • If using anonymous authentication, set the user to "Application pool identity" to be able to isolate your sites and applications

 

  • Do not store sensitive information in configuration files. Encrypt such fields if you need to have them:

Protecting Connection Strings and Other Configuration Information (C#)

https://docs.microsoft.com/en-us/aspnet/web-forms/overview/data-access/advanced-data-access-scenario...

 

  • Remove any unused modules to reduce attack surface. For example, if you do not specifically need WebDAV, do not install it.

  • Consider adding the host names of your web sites to Hosts file to point 127.0.0.1, so that you can test your applications locally on the servers in a web farm environment. This would be the first and the easiest test to eliminate network issues.

 

Application Performance

  • If your .NET/.NET Core application gets burst loads or for some reason you need to recycle your application or application pool during work hours, consider increasing minWorkerThreads and minIoThreads settings:

processModel Element (ASP.NET Settings Schema)

https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/7w2sway1%28v%3dvs.100%29

 

ProcessModelSection.MinWorkerThreads Property

https://docs.microsoft.com/en-us/dotnet/api/system.web.configuration.processmodelsection.minworkerth...

 

runtimeconfig.json

https://docs.microsoft.com/en-us/dotnet/core/run-time-config/#runtimeconfigjson

 

ASP.NET Thread Usage on IIS 7.5, IIS 7.0, and IIS 6.0

https://docs.microsoft.com/en-us/archive/blogs/tmarq/asp-net-thread-usage-on-iis-7-5-iis-7-0-and-iis...

 

  • For .NET Core applications, choose to run it "in-process":

In-process hosting model

https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/aspnet-core-module?view=aspnetcore-2.2#...

 

  • If your server has many CPU cores (like more than 16, for instance) consider making changes on GC settings:
    • Consider setting GC heap count to a lower number than the number of CPU core. While deciding heap count, take the number of applications hosted on the server into consideration

<GCHeapCount> element

https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcheapcount-ele...

 

Middle Ground between Server and Workstation GC

https://devblogs.microsoft.com/dotnet/middle-ground-between-server-and-workstation-gc/

 

  • Depending on the number of CPU cores on your server, consider setting CPU affinity of your application pools. For very high CPU consumption issues, leave at least one CPU core unused, so that you can access the server to collect logs to troubleshoot and be able to take actions towards resolution.

smpAffinitized and smpProcessorAffinityMask

https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/add/cpu

 

  • If your hardware or virtualization environment has NUMA support, consider setting "Maximum number of processes" to zero:

IIS 8.0 Multicore Scaling on NUMA Hardware

https://docs.microsoft.com/en-us/iis/get-started/whats-new-in-iis-8/iis-80-multicore-scaling-on-numa...

 

  • For ASP.NET applications, confirm that they are built in release mode, and that they do not have debug="true" in any web.config file in production

Debug Mode in ASP.NET Applications (the article is very old, but it is still valid for all .NET versions)

https://support.microsoft.com/en-us/help/2580348/debug-mode-in-asp-net-applications

 

  • Make sure tracing is installed (you never know when you would need it) but keep it disabled at both application and IIS level (Failed Request Tracing)

Troubleshooting Failed Requests Using Tracing in IIS 8.5

https://docs.microsoft.com/en-us/iis/troubleshoot/using-failed-request-tracing/troubleshooting-faile...

 

Trace Failed Requests Logging for a Site <traceFailedRequestsLogging>

https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/sites/site/tracefailedrequ...

 

  • Set the applications' timeout values as low as technically possible

Set Timeouts Aggressively
https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff647787(v=pandp.10)?redirectedfrom=MSDN#...

 

  • Make settings to make use of HTTP/2:

How do I use HTTP/2?

https://docs.microsoft.com/en-us/iis/get-started/whats-new-in-iis-10/http2-on-iis#how-do-i-use-http2

 

  • Install/copy these tools on the server as you never know when you will need them

PerfView

https://github.com/Microsoft/perfview

 

Debug Diagnostics Tool

https://www.microsoft.com/en-us/download/details.aspx?id=58210

 

ProcDump

https://docs.microsoft.com/en-us/sysinternals/downloads/procdump

 

ProcMon

https://docs.microsoft.com/en-us/sysinternals/downloads/procmon

 

  • Monitoring is the most important thing about performance. Know your applications performance baseline and monitor for any changes:

Azure Monitor overview

https://docs.microsoft.com/en-us/azure/azure-monitor/overview

 

What is Application Insights?

https://docs.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview

 

  • Save the performance counters one should monitor in case of an issue to the desktop or somewhere. The following might be a starting point for ASP.NET applications:

   \Processor Information(_Total)\% Processor Time
   \.NET CLR Exceptions(w3wp)\# of Exceps Thrown / sec
   \.NET CLR Memory(w3wp)\# Gen 0 Collections
   \.NET CLR Memory(w3wp)\# Gen 1 Collections
   \.NET CLR Memory(w3wp)\# Gen 2 Collections
   \.NET CLR Memory(w3wp)\# Induced GC
   \.NET CLR Memory(w3wp)\Process ID
   \.NET CLR Memory(w3wp)\#Bytes In All Heaps 
   \ASP.NET\Application Restarts
   \ASP.NET\Request Execution Time
   \ASP.NET\Requests Current
   \ASP.NET\Requests Queued

   \ASP.NET Applications(*)\Requests Executing
   \ASP.NET Applications(*)\Requests/Sec
   \Memory\Available MBytes
   \Process(w3wp)\% Processor Time
   \Process(w3wp)\ID Process
   \Process(w3wp)\Private Bytes

  \WAS_W3WP(*)\Total Health Pings.

14 Comments
Copper Contributor

Thank you @Cenk_Iscan 
The post is very clear and direct.
Good Job, with that information we can support better the IIS configurations.

Copper Contributor

Hi @Cenk_Iscan 

Thanks for the article. I have a question when you connect SQL server database using IIS and have application pool identity set through a domain user account for all application pools.

The user will be having permissions on database.

So is the recommendation is to use one user for all the applications or one application should connect to database using one user only?

Microsoft

Hi @ghamandipp 

Running each application on its own application pool with a different user is one of the isolation steps which is strongly recommended. For your scenario, using one gMSA user for each application would be a good idea.

Copper Contributor

Hi @Cenk_Iscan ,

So if we have 100 different applications, we can have 100 different isolated application pools. But having 100 different users to run each application would be a difficult task.

How it can be achieved?

Copper Contributor

Hi @Cenk_Iscan,

Have you compared best practices to STIG settings for IIS? There are some things that align with what you have stated and some that do not. For the most part most of the configuration settings mentioned can be done for us, however the application pool settings do give us fits based on application. Also the STIG settings do want these set:

  • private memory limit 
  • virtual memory limit

I do like this write up as it gives me a guideline to start from.

Microsoft

@marto871 Yes, there are some recommendations out in the wild that do not align with mine. And STIG has a few of them which do not align. But I will stick with my recommendations on application pool recycling settings; and especially about giving memory limits. If you check "virtual bytes" of any 64bit worker process at any random time, you would see terabytes. This makes giving a limit to virtual memory unnecessary, and even harmful - causing the pool to recycle too often. For both virtual and private memory limits, since they are not actually limits but recycle triggers, that does not make any sense.

 

There might be some exceptions for private memory limit (like known memory leak issues in the application), but I cannot think of any scenario one would need to set limit to virtual memory.

Copper Contributor

@Cenk_Iscan,

Thanks for the reply!

Copper Contributor

Hi! I've actually ran into issue this week related to this topic.  I haven't found a solution yet and the testing is still ongoing.  I work in an environment where our IIS servers have to be configured according to what is put forth by DISA STIGS for IIS, specifically the part about having to use memory values other than 0 for both private and memory limits.  The problem we are running into is something I caught during a migration going from on-prem to DISA cloud.  The on-prem IIS web server is built in a VMWARE hypervisor.  The IIS web server on the cloud is built in a KVM hypervisor.  Both IIS web servers are hosting the same application and using the same IIS configuration settings, and same RAM and app pool memory limits.  However, the difference is that the IIS web server that is using the KVM hypervisor is posting every minute for all app pools event ID 5077 in the system log that states the application pool has requested a recycle because it has reached its virtual memory limit.  The only conclusion I can come up with is that KVM manages memory differently than VMWARE. I did notice that the IIS application pools in VMWARE have a NUMA field that has a value of "maxavailablememory" in the CPU section.  The IIS app pool in KVM do not have this field. 

 

On the IIS VM that uses the KVM hypervisor, all of my applications have dedicated app pools and the only memory value that eliminates the issue is 0.  We've tried increasing all of the memory values.  

 

Has anyone ran into an issue like this before with a successful outcome, or have any suggestions that I could try to resolve the issue causing the 5077 events that are continuously posting? 

Iron Contributor

@Cenk_Iscancan you please recommend some semi-automated tools and scripts for hardening IIS? I'm especially interested in hardening according to CIS standards. Something with PowerShell or DSC. I tried to get some advice in IIS forums but it seems there are not many people there... Here is my IIS forum post -

https://techcommunity.microsoft.com/t5/microsoft-iis/iis-hardening-with-cis-standards-tools-and-opti...

Microsoft

@Sergg  I'm not aware of any publicly available tool or script for hardening IIS. I have quite a few customers who put together some Powershell scripts according to their needs and they are all different from each other. Some of them even have more than one script for their different applications. So, I'd recommend deciding which CIS or other recommendations you will apply to your own server and create a script accordingly.

Microsoft

@Mparcher If I'm not mistaken, 5077 event is logged when you set limit to virtual memory of the application pool. I strongly recommend removing that limit, no matter what behavior difference you see between different environments. The only solution to random recycling is removing that limit. 

Copper Contributor

guys,

what would be the benefit of setting IdleTimeout to 0? I guess this is to avoid the fist visitor slow down. But in long-term anyway, the worker that never restarts, can slow the site down. 

Microsoft

@Alex83995 That really depends on the application running on that pool. Even if it needs to be recycled every now and then, that should not be at some random time, but you should set specific times instead.

Copper Contributor

Hi @Cenk_Iscan I keep observing that our Monitoring Agent Service is stopped when IIS is installed. I then see a peak in memory usage or low memory condition before the service is stopped. In this case, what is best practicies for IIS from your point of view? The server has enough RAM and pagefile.

Version history
Last update:
‎Apr 17 2020 01:21 AM
Updated by: