An ASP.NET application that allocates steadily until it is recycled on a schedule is not leaking by accident — it is usually hitting a limit that nobody tuned. IIS ships with private and virtual memory limits set to zero, meaning no limit at all, yet recycled pools and out-of-memory kills turn up in production because a platform default was inherited from a template.
This covers what each limit does, how to read the counters that tell you whether you need one, and how to set values that suit a long-running .NET service. It applies to both .NET Framework and ASP.NET Core applications hosted behind the ASP.NET Core Module. our Windows VPS comparison table if you are still choosing the memory tier for a workload of this kind.
What the two limits actually control
Under IIS Manager → Application Pools → your pool → Advanced Settings → Recycling there are two memory thresholds. Private Memory Limit caps the committed private bytes of a worker process — the memory only that process can see. Virtual Memory Limit caps the whole virtual address space, which for a 64-bit process is enormous and rarely the right trigger. Set the private limit and leave the virtual limit alone unless you are debugging a specific address-space problem.
# Read current memory limits for a pool
Import-Module WebAdministration
Get-ItemProperty IIS:\AppPools\myapp |
Select-Object name, recycling.periodicRestart.privateMemory,
recycling.periodicRestart.memory
Values are in kilobytes. A setting of 0 means unlimited, which is the default and the reason a runaway allocation can take the whole server down rather than recycling one pool.
Decide the number from measurement, not guesswork
Set the limit above the normal working set of a healthy instance, not at it. If a healthy process settles around 700 MB after warm-up and peaks near 1.1 GB during daily batch work, a limit of 1.5 GB recycles only genuinely abnormal growth. Setting it at 800 MB would recycle a perfectly healthy application several times a day and add latency for no benefit.
Use Performance Monitor to establish the baseline. Two counters matter most: Process\Private Bytes for the w3wp instance and .NET CLR Memory\# Bytes in all Heaps to separate managed from native allocation. The gap between them tells you whether growth is managed objects or unmanaged buffers, and that distinction points at different fixes.
- Watch over at least a full business cycle, including the heaviest batch window.
- Record the steady-state value and the routine peak.
- Set the private memory limit roughly 30 to 50 percent above the routine peak.
- Confirm the app pool still recycles cleanly and comes back warm.
Distinguish a limit from an actual leak
If the working set climbs monotonically across days and never flattens, a limit is a bandage. Take two full memory dumps a few hours apart and compare object counts, or use the .NET diagnostics tools to see which type is accumulating. Genuine growth with no plateau is a managed leak; growth that plateaus and then steps up at predictable intervals is usually caching that is working as designed.
Overlapped recycle is the other lever here. With overlapping enabled, IIS starts the replacement worker before the old one drains, so a memory-triggered recycle does not produce a burst of 503 responses while the new process warms up.
Set-ItemProperty IIS:\AppPools\myapp -Name recycling.disallowOverlappingRotation -Value $false
# Generate an event log entry on recycle so you can correlate it with latency
Set-ItemProperty IIS:\AppPools\myapp -Name recycling.logEventOnRecycle -Value 'Time,Memory,Requests'
Logging recycle events is what turns a mystery latency spike into a one-line explanation. If recycles correlate with your slow periods, the limit is too tight; if they correlate with a deployment, the leak was introduced there.
Related settings worth reviewing at the same time
Memory limits interact with three other pool settings. The idle timeout will shut a pool down after twenty minutes without requests by default, which ruins warm caches for applications with sparse traffic. The regular time interval recycles every 1,740 minutes whether or not the application needs it. And the process model identity affects how much kernel memory the pool consumes under load.
- Idle Time-out — raise it or set to 0 for background workers that must stay warm.
- Regular Time Interval — set to 0 to rely on memory and request-count triggers only.
- Maximum Worker Processes — leave at 1 unless you have built a genuinely stateless app; a web garden changes session behaviour.
Set pool limits in context of the whole machine
A private memory limit that is generous in isolation can still starve the server when five pools share the same RAM. Add the limits of every pool on the host, add the operating system’s own footprint, and confirm the total still fits with room for the file cache that IIS relies on for static content. If the sum does not fit, the correct answer is more memory or fewer pools — not a tighter limit that turns into a recycling loop under peak load.
Tuning these settings together keeps a memory-intensive application stable without the reflexive response of recycling on a timer. Match the pool configuration to the workload you actually run rather than to a default template, and revisit the numbers whenever the application or its traffic profile changes. For a workload of this shape, see the full specs and pricing to confirm the instance you have has the memory headroom the pool limits assume.



