A default IIS installation is tuned for safety, not throughput. On a 4-core server handling 250–300 concurrent users of an ASP.NET application, the following changes took our client's site from regular "Service Unavailable" errors at peak to stable response times. Measure before and after every change — your workload may differ.
1. Application pool settings
- Queue Length: the default of 1000 requests is usually fine; raise it only if you see HTTP 503 errors while CPU is still available.
- Idle Time-out: the default of 20 minutes shuts the worker process down after quiet periods, so the next visitor pays the start-up cost. Set it to
0for busy production sites. - Regular Time Interval recycling: the default recycles every 1740 minutes (29 hours), which eventually lands in the middle of the working day. Disable it and schedule a recycle at a specific low-traffic time instead.
- Enable 32-bit applications: leave this off unless a component requires it, so the process can use more memory.
2. ASP.NET concurrency
From .NET 4.0, maxConcurrentRequestsPerCPU defaults to 5000, which is generous. If you are on an older framework or the value has been lowered, set it in aspnet.config:
<system.web>
<applicationPool maxConcurrentRequestsPerCPU="5000"
maxConcurrentThreadsPerCPU="0"
requestQueueLimit="5000" />
</system.web>
For applications that make many outbound HTTP calls, the default limit of two connections per remote host in older .NET versions is a common bottleneck. Raise it in web.config:
<system.net>
<connectionManagement>
<add address="*" maxconnection="48" />
</connectionManagement>
</system.net>
3. Compression and caching
- Enable both static and dynamic compression. Dynamic compression costs CPU, so watch processor time after enabling it.
- Set long client cache headers for versioned static files (CSS, JS, images).
- Use output caching for pages or fragments that are identical for all users, and let the kernel-mode cache serve them where possible.
4. Logging and modules
Remove IIS modules and handlers the application does not use, and log only the fields you need. On very busy servers, writing logs to a separate disk avoids contention with the application.
5. Avoid web gardens unless you have tested them
Setting Maximum Worker Processes above 1 creates a web garden. It can help CPU-bound applications, but it breaks in-process session state and in-memory caches. Move session state out of process first, then load-test.
What to monitor
Track these Performance Monitor counters during load tests and in production:
ASP.NET\Requests Queued— should stay near zero.ASP.NET Applications\Requests/Sec— your throughput.Processor\% Processor Time— sustained values above 80% mean you need to optimise code or scale out..NET CLR Memory\% Time in GC— high values point to allocation-heavy code.
Server tuning buys headroom, but it does not fix slow database queries. In our experience the biggest wins after these settings came from indexing and caching at the data layer.
