Application Pool Recycling Settings
Recommended App Pool Recycling Settings
Regular Time Interval (minutes)
Recycles the app pool at fixed intervals (default: 1740 minutes)
Disable this unless you’re dealing with memory leaks. Use scheduled times instead.
Specific Times
Recycles the pool at a specific time of day
Recommended: Set to once daily during off-peak hours (e.g., 03:00 AM
)
Private Memory Limit (KB)
Recycles if private memory exceeds this threshold
Useful for memory leak mitigation. Use cautiously—set after monitoring baseline memory usage.
Virtual Memory Limit (KB)
Recycles if virtual memory exceeds this threshold
Avoid unless explicitly needed.
Idle Timeout
Stops the worker process if idle for a set period
Keep default 20 minutes
or increase if needed. Avoid low values, which can stop the pool prematurely.
Common Issues After Recycling (and How to Fix Them)
Cold Start / Slow First Request
After recycling, IIS doesn't load the application until the first request arrives, causing delays.
Solution:
Set AppPool Start Mode to
AlwaysRunning
Enable Preload on the Application
powershellCopyEditSet-ItemProperty IIS:\AppPools\YourAppPool -Name startMode -Value AlwaysRunning
Set-WebConfigurationProperty `
-Filter "/system.applicationHost/sites/site[@name='YourSiteName']/application[@path='/']" `
-Name preloadEnabled `
-Value True
App Pool Stops or Crashes
After recycling, the app pool may fail to start if the application crashes or initialization fails.
Solution:
Check Event Viewer for Application/ System logs
Enable Failed Request Tracing (FREB) for detailed debugging
Check for misconfigured connection strings, missing DLLs, or exceptions in startup logic
Ping Failures & Rapid-Fail Protection
If IIS health checks (pings) to the worker process fail during or after recycling, the pool may stop.
Solution:
Ensure these settings:
Ping Enabled:
True
Ping Maximum Failures:
5 or more
Rapid-Fail Protection: Consider disabling temporarily during troubleshooting
powershellCopyEditSet-ItemProperty IIS:\AppPools\YourAppPool -Name failureRapidFailProtection -Value False
Warm-Up Strategy
To avoid cold-start delays after recycle:
Implement a Warm-Up URL that is automatically requested after recycle.
Use Application Initialization module in IIS.
Or schedule a PowerShell/Task Scheduler script to make requests post-recycle.
Example PowerShell warm-up snippet:
powershellCopyEditInvoke-WebRequest "https://yourapp.com/healthcheck" -UseBasicParsing
Request Queuing or Loss During Recycle
If many requests are sent during recycle, they may be queued or dropped.
Solution:
Increase queue length:
powershellCopyEditSet-ItemProperty IIS:\AppPools\YourAppPool -Name queueLength -Value 1000
Use load balancer health checks to take node offline during recycle
Enable overlapping recycling for zero-downtime deployments
Best Practices Summary
Disable automatic interval-based recycling unless necessary.
Set a single recycle time during off-peak hours.
Monitor memory usage before enabling memory-based recycle triggers.
Use AlwaysRunning + preloadEnabled to avoid slow first requests.
Avoid InProc session state if you recycle frequently—prefer
SQL
orStateServer
.Use monitoring tools (e.g., Event Viewer, SCOM, App Insights) to detect failures early.
param (
[switch]$AuditOnly,
[switch]$FixAll,
[string[]]$FixAppPools,
[int]$QueueLength = 2000,
[string]$StartMode = "AlwaysRunning",
[int]$IdleTimeout = 0,
[bool]$PingEnabled = $false,
[int]$PrivateMemory = 0,
[int]$RecycleTime = 0,
[string]$HtmlOutput = "C:\\Reports\\IIS_AppPool_Audit.html"
)
Import-Module WebAdministration
function Get-AppPoolAuditTable {
$result = @()
$appPools = Get-ChildItem IIS:\AppPools
foreach ($pool in $appPools) {
$props = Get-ItemProperty "IIS:\\AppPools\\$($pool.Name)"
$current = @{
Name = $pool.Name
QueueLength = $props.queueLength
StartMode = $props.startMode
IdleTimeout = $props.processModel.idleTimeout.TotalMinutes
PingEnabled = $props.processModel.pingingEnabled
PrivateMemory = $props.recycling.period.privateMemory
RecycleTime = $props.recycling.period.time.TotalMinutes
}
$expected = @{
QueueLength = $QueueLength
StartMode = $StartMode
IdleTimeout = $IdleTimeout
PingEnabled = $PingEnabled
PrivateMemory = $PrivateMemory
RecycleTime = $RecycleTime
}
$result += [PSCustomObject]@{
"AppPool" = $current.Name
"QueueLength" = "$($current.QueueLength) / $($expected.QueueLength)" + $(if ($current.QueueLength -eq $expected.QueueLength) {" ✅"} else {" ⚠️"})
"StartMode" = "$($current.StartMode) / $($expected.StartMode)" + $(if ($current.StartMode -eq $expected.StartMode) {" ✅"} else {" ⚠️"})
"IdleTimeout(min)" = "$($current.IdleTimeout) / $($expected.IdleTimeout)" + $(if ($current.IdleTimeout -eq $expected.IdleTimeout) {" ✅"} else {" ⚠️"})
"PingEnabled" = "$($current.PingEnabled) / $($expected.PingEnabled)" + $(if ($current.PingEnabled -eq $expected.PingEnabled) {" ✅"} else {" ⚠️"})
"PrivateMemory(KB)" = "$($current.PrivateMemory) / $($expected.PrivateMemory)" + $(if ($current.PrivateMemory -eq $expected.PrivateMemory) {" ✅"} else {" ⚠️"})
"RecycleTime(min)" = "$($current.RecycleTime) / $($expected.RecycleTime)" + $(if ($current.RecycleTime -eq $expected.RecycleTime) {" ✅"} else {" ⚠️"})
}
}
return $result
}
function Export-HtmlReport {
param (
[array]$Table,
[string]$OutputPath
)
$folder = Split-Path $OutputPath -Parent
if (-not (Test-Path $folder)) {
New-Item -Path $folder -ItemType Directory -Force | Out-Null
}
$html = @"
<html><head>
<title>IIS AppPool Audit Report</title>
<style>
body { font-family: Segoe UI, sans-serif; font-size: 14px; }
table { border-collapse: collapse; width: 100%; margin-top: 20px; }
th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.ok { background-color: #d4edda; }
.warn { background-color: #fff3cd; }
</style>
</head><body>
<h2>IIS Application Pool Audit Report - $(Get-Date -Format 'yyyy-MM-dd HH:mm')</h2>
<table><tr>
"@
foreach ($col in $Table[0].PSObject.Properties.Name) {
$html += "<th>$col</th>"
}
$html += "</tr>"
foreach ($row in $Table) {
$html += "<tr>"
foreach ($col in $row.PSObject.Properties) {
$class = if ($col.Value -like "*⚠️*") { "warn" } elseif ($col.Value -like "*✅*") { "ok" } else { "" }
$html += "<td class='$class'>$($col.Value)</td>"
}
$html += "</tr>"
}
$html += "</table>"
$html += @"
<h3>Recommended AppPool Settings (based on WSUS performance best practices)</h3>
<table>
<tr><th>Setting</th><th>Recommended Value</th><th>Reason</th></tr>
<tr><td>QueueLength</td><td>$QueueLength</td><td>Prevents throttling during scan storms</td></tr>
<tr><td>StartMode</td><td>$StartMode</td><td>Ensures warm start and stability</td></tr>
<tr><td>IdleTimeout</td><td>$IdleTimeout</td><td>Disables automatic shutdown of the worker process</td></tr>
<tr><td>PingEnabled</td><td>$PingEnabled</td><td>Disables unnecessary pings that could trigger recycling</td></tr>
<tr><td>PrivateMemory</td><td>$PrivateMemory</td><td>Prevents memory-triggered recycling that disrupts metadata caching</td></tr>
<tr><td>RecycleTime</td><td>$RecycleTime</td><td>Disables default 29-hour recycle that flushes cache</td></tr>
</table>
</body></html>
"@
$html | Out-File -Encoding UTF8 -FilePath $OutputPath
Write-Host "HTML report saved to: $OutputPath" -ForegroundColor Cyan
}
function Fix-AppPoolSettings {
$appPools = if ($FixAppPools) {
Get-ChildItem IIS:\AppPools | Where-Object { $FixAppPools -contains $_.Name }
} else {
Get-ChildItem IIS:\AppPools
}
foreach ($pool in $appPools) {
Set-ItemProperty $pool.PSPath -Name queueLength -Value $QueueLength
Set-ItemProperty $pool.PSPath -Name startMode -Value $StartMode
Set-ItemProperty $pool.PSPath -Name recycling.period.privateMemory -Value $PrivateMemory
Set-ItemProperty $pool.PSPath -Name recycling.period.time -Value ([TimeSpan]::FromMinutes($RecycleTime))
Set-ItemProperty $pool.PSPath -Name processModel.idleTimeout -Value ([TimeSpan]::FromMinutes($IdleTimeout))
Set-ItemProperty $pool.PSPath -Name processModel.pingingEnabled -Value $PingEnabled
Write-Host "Fixed settings for AppPool: $($pool.Name)" -ForegroundColor Green
}
}
$table = Get-AppPoolAuditTable
$table | Format-Table -AutoSize
if ($HtmlOutput) {
Export-HtmlReport -Table $table -OutputPath $HtmlOutput
}
if (-not $AuditOnly -or $FixAll -or $FixAppPools) {
Fix-AppPoolSettings
}
Usage
Parameters
-AuditOnly
Switch
N/A
Only audits settings. No changes made.
-FixAll
Switch
N/A
Fixes all App Pools using provided or default best practice values.
-FixAppPools
string[]
N/A
Names of specific App Pools to fix. Use with overrides.
-QueueLength
int
2000
Max number of queued requests before rejecting new ones.
-StartMode
string
"AlwaysRunning"
Pool startup mode (AlwaysRunning
, OnDemand
).
-IdleTimeout
int
0
Idle timeout in minutes (0
disables timeout).
-PingEnabled
bool
$false
Whether IIS pings the worker process.
-PrivateMemory
int
(KB)
0
Private memory limit (0 = unlimited).
-RecycleTime
int
(min)
0
Periodic recycle interval (0 = disable).
-HtmlOutput
string
N/A
Full file path to generate audit report in HTML format.
Audit Examples
Basic Audit to Console
.\IISRecycleAuditAndFix.ps1 -AuditOnly
Audit with HTML Output
.\IISRecycleAuditAndFix.ps1 -AuditOnly -HtmlOutput "C:\Reports\IIS\AppPoolAudit.html"
Fix Examples
Fix All App Pools to Default Recommendations
.\IISRecycleAuditAndFix.ps1 -FixAll
Fix All Pools + Audit to HTML
.\IISRecycleAuditAndFix.ps1 -FixAll -HtmlOutput "C:\Reports\IIS\FixedReport.html"
Fix All Pools with Custom Parameters
.\IISRecycleAuditAndFix.ps1 -FixAll -IdleTimeout 10 -RecycleTime 0 -PingEnabled $false
Targeted App Pool Fixes
Fix Only Specific Pools (Default Values)
.\IISRecycleAuditAndFix.ps1 -FixAppPools @("WsusPool", "MyAppPool")
Fix Specific Pools with Custom Settings
.\IISRecycleAuditAndFix.ps1 -FixAppPools @("SecureAPI") -IdleTimeout 15 -PrivateMemory 0 -QueueLength 1500
Advanced Automation Example
Audit and Fix Targeted Pools, Export to HTML with Timestamp
$time = Get-Date -Format 'yyyyMMdd_HHmm'
$report = \"C:\\Reports\\IIS\\Audit_$time.html\"
.\IISRecycleAuditAndFix.ps1 `
-FixAppPools @(\"MyAPI\", \"WsusPool\") `
-IdleTimeout 5 `
-RecycleTime 0 `
-HtmlOutput $report
What Is Audited and Fixed
QueueLength
Max queued requests
Prevents overload rejections
StartMode
App pool startup behavior
AlwaysRunning ensures availability
IdleTimeout
Auto shutdown delay
Disabling avoids mid-use shutdown
PingEnabled
Process health ping
Avoid false recycling triggers
PrivateMemory
Memory-based recycle
Unlimited reduces unnecessary resets
RecycleTime
Periodic recycle timer
Disabling prevents unexpected resets
HTML Report Includes
Table: Per-App Pool current vs. recommended values
Color indicators: ✅ OK, ⚠️ Needs Attention
Full recommendations section with rationale for each setting
Detailed Explanation of App Pool Settings
Queue Length
Definition: The maximum number of requests that IIS will queue for an application pool before rejecting new requests.
Default: 1000
Recommended: 2000 (or higher depending on expected concurrent traffic)
Why it matters: Increasing the queue length helps prevent dropped requests during traffic spikes. A low queue length can cause 503 errors when the limit is reached, especially during WSUS scan storms or high-load conditions.
Start Mode
Definition: Determines whether the application pool should be started immediately when IIS starts (
AlwaysRunning
) or only when the first request arrives (OnDemand
).Default:
OnDemand
Recommended:
AlwaysRunning
Why it matters: With
AlwaysRunning
, application pools are ready to serve requests immediately, reducing cold-start latency. This is especially important for APIs or services like WSUS, where performance and availability are critical.
Idle Timeout
Definition: The amount of idle time (in minutes) after which IIS shuts down the worker process to conserve resources.
Default: 20 minutes
Recommended: 0 (Disabled)
Why it matters: Disabling idle timeout ensures that the worker process remains alive even during periods of inactivity. This is critical for services like WSUS where unpredictable scan patterns could otherwise trigger process shutdown and slow restarts.
Ping Enabled
Definition: Indicates whether IIS periodically pings the worker process to check if it is healthy.
Default:
True
Recommended:
False
Why it matters: While pings can help detect unresponsive processes, they may falsely classify long-running WSUS scans or metadata loads as unresponsive, causing premature app pool recycling. Disabling pings avoids these false positives.
Private Memory Limit
Definition: The maximum amount of private (non-shared) memory the worker process can use before being recycled (in KB).
Default: 1,843,200 KB (~1.8 GB)
Recommended: 0 (Unlimited)
Why it matters: WSUS and similar services can require large in-memory caches (10–24 GB+). Limiting private memory can trigger frequent recycling, disrupting service and cache rebuilds. Setting to 0 allows the app pool to grow as needed.
Recycle Time (Regular Time Interval)
Definition: The interval (in minutes) after which IIS automatically recycles the application pool, regardless of memory usage or activity.
Default: 1740 minutes (29 hours)
Recommended: 0 (Disabled)
Why it matters: Automatic recycling can unexpectedly interrupt client operations or cause metadata reloads, especially during critical WSUS scans. Disabling it increases stability, provided memory and CPU are properly monitored.
Last updated