Skip to main content

How to limit memory usage of applications in IIS

A while back I talked about a memory leak we had in one of our applications. As a consequence, it brought the full production environment to a halt impacting not only the causing application but all applications hosted on the same IIS instance.

Although we found the root cause and fixed the problem, we did a post-mortem to discuss on how to avoid this in the future. In this post, I'll walk you through the practical strategies we implemented to limit and optimize memory usage for our applications running in Internet Information Services (IIS).

n this guide, I'll walk you through practical strategies to limit and optimize memory usage for applications running in Internet Information Services (IIS).

Understanding memory usage in IIS

Before diving into solutions, it's important to understand how IIS manages memory. IIS runs web applications in application pools, which are processes (w3wp.exe) that host your web applications. Each application pool can consume memory independently, and without proper configuration, a single problematic application pool can negatively impact the entire server.

There are 2 distinct settings to manage memory usage in IIS:

  • private memory limit
  • virtual memory limit

Private memory limit

The private memory limit is the maximum amount of private memory (physical RAM) that a worker process in an application pool can use. It applies to the memory allocated exclusively to the process, not shared with other processes.

If the private memory usage of the worker process exceeds this limit, IIS will recycle the application pool to free up resources.

Virtual memory limit

The virtual memory limit is the maximum amount of virtual memory (address space) that a worker process in an application pool can use. It includes both physical memory and memory mapped to disk (e.g., page file).

Important: An application could claim up front a lot of virtual memory, so it is recommended to stay away from this setting and focus mainly on the private memory limit when limiting memory usage.

Key strategies for limiting memory usage

Configure Recycling Settings

Application pool recycling is one of the most effective ways to control memory usage:

  1. Open IIS Manager
  2. Select your application pool
  3. Click on "Recycling" in the Actions pane
  4. Configure the following settings:
    • Fixed Interval Recycling: Schedule recycling at regular intervals
    • Memory-Based Recycling: Set a memory threshold (in KB) that triggers recycling
    • Request Limit Recycling: Recycle after processing a specific number of requests
    • Specific Time Recycling: Schedule recycling at off-peak hours


Configure private memory limit

As already mentioned, you can set a maximum private memory limit for your application pool:

  1. Open IIS Manager
  2. Select your application pool
  3. Click on "Advanced Settings"
  4. Set "Private Memory Limit (KB)" to your desired value (e.g., 1048576 for 1GB)

When an application pool reaches this limit, IIS will automatically recycle it, freeing up memory.

Implement request limits

Set limits on request size and execution timeout:

Conclusion

Managing memory usage in IIS requires a multi-faceted approach that combines proper IIS configuration with application optimization. By implementing the strategies outlined above, we significantly improved the stability and performance of our web applications.

Of course all of this should not be an excuse to not optimize your application code first. Some tips:

  • Dispose of resources properly: Ensure all disposable objects are properly disposed
  • Avoid memory leaks: Check for common patterns that cause memory leaks
  • Use caching judiciously: Excessive caching can consume large amounts of memory
  • Implement proper object pooling: Reuse expensive objects when possible
  • Enable server GC: Configure .NET to use the server garbage collection mode

More information

Visual Studio 2022 - Check for memory leaks

Azure Monitor Log Analytics–Identify high memory usage

IIS Best Practices | Microsoft Community Hub

iis 7 - How to limit the memory used by an application in IIS? - Server Fault

Popular posts from this blog

Podman– Command execution failed with exit code 125

After updating WSL on one of the developer machines, Podman failed to work. When we took a look through Podman Desktop, we noticed that Podman had stopped running and returned the following error message: Error: Command execution failed with exit code 125 Here are the steps we tried to fix the issue: We started by running podman info to get some extra details on what could be wrong: >podman info OS: windows/amd64 provider: wsl version: 5.3.1 Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM Error: unable to connect to Podman socket: failed to connect: dial tcp 127.0.0.1:2655: connectex: No connection could be made because the target machine actively refused it. That makes sense as the podman VM was not running. Let’s check the VM: >podman machine list NAME         ...

Cache stampede: when our cache turned against us

While investigating some performance issues, we ran into an ASP.NET Core API that cached a fairly expensive aggregation query for 60 seconds. Under normal load, that was fine: one request rebuilds the cache, everyone else reads from it. Under peak load, dozens of requests would arrive in that same expiry window, all see a cache miss, and all fire the same expensive query in parallel. The database didn't like that. That was the moment when our caching layer stopped helping and started hurting. A burst of requests comes in at the same time, all miss the cache, and all go hammer the database or the downstream API at once. That's a cache stampede . The cache was supposed to protect our backend, and for a few hundred milliseconds it did the opposite. Why this happens IMemoryCache.GetOrCreate (and its async sibling) looks like it protects you, but it doesn't add any locking on its own. Look at the naive version: public async Task<Report> GetReportAsync(string key) ...

A complex system designed from scratch never works

A few years ago, I worked as an architect on a big mainframe rewrite. I still count it as one of my failures. Not because the technology was wrong, but because I couldn't convince the management team to simplify the approach. Years later, the organization is still struggling to get the new system up and running. I left the project at the time, because I couldn't put my name behind an approach that would take very long and cost a lot of money without a working system to show for it along the way. Gall’s Law That memory keeps coming back to me, because it's a textbook case of Gall's Law playing out in real life. Gall's Law , from John Gall's Systemantics , states it plainly: A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works, and it cannot be patched to make it work. You have to start over with a simple system that works. What does that mean in practice,...