---
title: "Nothing Was Wrong With the App Code, Yet Requests Stalled"
canonical: https://dxdev.com/blog/cockpit-iis-nap/
datePublished: 2026-05-13
---
A request to the site would go in and nothing would come back for thirty, forty, sixty seconds. Then the response would arrive as if the server had been thinking very hard about it. No 500, no TCP reset, no timeout. It just hung.

Our first instinct was application code. Something blocking on an external call, a database query sitting on a lock, an LLM request that had gone sideways. We pulled logs. We checked the process. Nothing looked obviously wrong, so we kept looking in the app.

The cause was IIS's DefaultAppPool, which had recycled itself after twenty minutes of no traffic.

## Twenty minutes of silence and IIS goes to sleep

Every IIS application pool has an idle-timeout setting, and the default is 20 minutes. If no requests come in for that long, IIS shuts down the worker process to save memory. The next request has to restart the whole process from scratch: load the application, initialize dependencies, warm up whatever caches the app keeps in memory. For a small side project with sporadic traffic, that is fine. For an AI-facing app where request latency already has a floor of two or three seconds, a cold start that adds thirty seconds looks exactly like an outage.

The site had been assigned to DefaultAppPool during initial setup, and DefaultAppPool has idle-timeout on. Nobody touched it. The app sat quiet long enough to get shut down, and when we came back to it we got thirty seconds of silence while IIS rebuilt the world.

## Two settings and one new pool

We recovered the stalled pool from the IIS manager, confirmed the app came back responsive, then made two changes.

First, we set `idleTimeout` to zero on the pool the app was using. Zero means never shut down the worker process due to idle time, so the pool stays warm as long as the app pool itself is running.

Second, we moved the app off DefaultAppPool and onto its own dedicated pool. DefaultAppPool is shared. Anything else on the same IIS box that crashes, gets recycled, or hits its own resource limits takes down every app assigned to it. A named pool gives the app an isolated lifecycle, so something else going sideways no longer drags it along.

Both changes took about five minutes in the GUI. The site came up clean and stayed up.

## The fix lived only on that machine

We sat with it for a minute after closing the IIS manager. The changes were live and the site was healthy, but we had not written any of it down anywhere a machine could read.

The `idleTimeout` setting lives in the IIS metabase, a machine-local XML configuration. If we rebuild the box, re-provision the server, or move to a new host, none of these settings come along. They exist as machine state, visible only to whoever next opens the IIS manager and thinks to look. Somebody would have to know to carve the app out of DefaultAppPool and set `idleTimeout=0`, and right now that depended on memory. That is a ritual, not a fix.

So we opened the IIS setup script, the PowerShell file that provisions the box's initial configuration, and added the carve-out explicitly:

```powershell
New-WebAppPool -Name "cockpit-pool" -Force
Set-ItemProperty "IIS:\AppPools\cockpit-pool" processModel.idleTimeout ([TimeSpan]::FromMinutes(0))
Set-ItemProperty "IIS:\AppPools\cockpit-pool" recycling.periodicRestart.time ([TimeSpan]::FromMinutes(0))
```

Now when someone provisions a fresh box and runs the setup script, the app gets its own named pool with idle-timeout off and periodic restart off, and nobody has to remember why.

## What else is sitting in machine state

The note we wrote up afterward ended with a question: what other deployment assumptions are still living in machine state instead of the setup script?

We do not have a full answer yet. The IIS pool config was the visible one because it produced a hang. There are probably others. Log rotation. Scheduled tasks. Environment variable blocks that got set by hand during a debugging session and never made it into a config file. Things that work right now because the machine is in the state it was in when somebody last touched it, and a fresh box built from the setup script would not reproduce that state.

We will probably find the next one the same way we found this one, after it fails.

## Provisioning is where fixes live

The heroic restart, the manual GUI click and the one-time incantation at the command line get a service back up. Keeping it up takes something else. Every manual fix encodes knowledge that isn't written anywhere else, and that knowledge belongs in the setup script instead of in the memory of whoever happened to be on call. The durable version of any infrastructure fix is the one a fresh machine would apply automatically.

So here is a question worth putting to your own team: which server settings exist only as machine state? A fix made in a GUI on one machine will not come back on a rebuilt one.
