Application Pool Recycling Settings

Setting
Description
Recommendation

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 or StateServer.

  • 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

Parameter
Type
Default
Description

-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

Setting
Description
Why It Matters

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