- sc-status paired with sc-substatus and sc-win32-status pinpoints the exact failure category, not just the HTTP code
- Failed Request Tracing only writes an XML trace when a request actually matches a configured failure-definition rule, so it produces no overhead on requests that don't match
- HTTP 502.3 originates from Application Request Routing or another reverse proxy, not from the IIS worker process itself
- HTTP 503 with a Service Unavailable page almost always means the application pool is stopped, not that the site is misconfigured
Why Logging and Performance Diagnostics Matter
Most IIS problems that reach an administrator's desk are reported vaguely: "the site is slow," "we got an error page," "it works sometimes." None of that is actionable on its own. IIS ships with three independent diagnostic systems that, used together, turn a vague complaint into a root cause: W3C logs for historical request-level data, Failed Request Tracing for deep per-request traces on demand, and Performance Monitor counters for real-time resource pressure. Each answers a different question. Logs tell you what happened and when. FREB tells you why a specific request failed, step by step through the pipeline. Counters tell you whether the server is currently under stress. Reaching for the wrong one wastes time — pulling FREB traces to answer a capacity question, or staring at Task Manager to explain a single failed request, are both dead ends. The rest of this article treats them as a toolkit, then applies that toolkit to the three status codes IIS administrators see most often in production: 500.19, 502.3, and 503.
W3C Logging Format and the Fields Worth Watching
IIS supports several log formats, but W3C Extended Log File Format is the one worth standardizing on: it's a plain-text, space-delimited format with a self-describing header and a customizable field list, and it's what every log analysis tool expects. Logs live under %SystemDrive%\inetpub\logs\LogFiles by default, one subfolder per site, with a new file created daily unless you change the rollover policy.
The default field set is thin. In IIS Manager, under a site's Logging feature, click Select Fields and add these if they aren't already present:
sc-statusandsc-substatus— the HTTP status and IIS's more specific sub-status.sc-statusalone hides the real story; a500with substatus19is a config problem, while a500with substatus0is an unhandled application exception. Always read them as a pair.sc-win32-status— the underlying Win32 error code. This is what separates "access denied" (5) from "file not found" (2) from "the specified network name is no longer available" (64), all of which can otherwise surface as the same generic HTTP status.time-taken— request duration in milliseconds. This is your primary slow-request signal; sort or filter on it to find the requests dragging down perceived performance, rather than relying on user reports of "slow."cs-uri-stemandcs-uri-query— the path and query string. Together withtime-taken, these tell you which endpoints are actually expensive, as opposed to which ones are merely called often.cs-usernameandc-ip— useful for correlating failures to a specific user or client when troubleshooting reports that only affect one account or one network segment.
A practical habit: filter a day's log for sc-status values of 4xx and 5xx, then sort by time-taken descending within that filtered set. Slow errors and fast errors usually have different causes — a slow 500 often points to a downstream dependency timing out before the app fails, while a fast 500 usually points to something failing immediately, like a null reference or a missing config section.
Failed Request Tracing (FREB) for Hard-to-Reproduce Issues
W3C logs tell you a request failed; they don't tell you where in the pipeline it failed or what each module did along the way. Failed Request Tracing (branded FREB, for Failed Request Event Buffering) closes that gap by capturing a detailed, step-by-step XML trace of individual requests as they move through IIS — module by module, with timings and outcomes for each stage.
FREB requires the Tracing role service (Web Server > Health and Diagnostics > Tracing) to be installed, and it must be enabled per site under Configure Failed Request Tracing, where you set a log directory and a maximum trace file count. The critical piece is the failure definition: a rule specifying which requests get traced, based on status code ranges, time-taken thresholds, or specific event severities. This is what makes FREB practical for production — you don't trace every request, only ones matching the rule, so there's no meaningful performance cost on the requests you don't care about.
This targeting is exactly what makes FREB the right tool for issues you can't reproduce on demand: an error that shows up once a day under real traffic, or a request that occasionally takes 30 seconds instead of 300ms. Set a rule to catch status-codes(500-599) or time-taken(30000), let it run, and the next occurrence produces a full trace automatically — no need to be watching when it happens. Each trace is written as an .xml file readable directly in a browser (with the accompanying freb.xsl stylesheet in the same folder) or in IIS Manager's trace log viewer. The trace shows every module invoked, in order, with the elapsed time each one took and the exact point of failure — which is precisely the detail a W3C log entry with a single time-taken value cannot provide.
Key Performance Counters to Watch
Logs and traces are retrospective — they explain what already happened. Performance counters, monitored through Performance Monitor (perfmon) or collected into a monitoring system, tell you what's happening right now and give early warning before users notice.
- Web Service > Current Connections and ASP.NET > Requests Current — the number of requests actively being processed or queued. A steady climb with no corresponding drop indicates the server is falling behind, not just busy.
- ASP.NET > Requests Queued — requests waiting for a free thread. This should sit near zero under normal load. A nonzero and growing value means the app is not processing requests as fast as they arrive — this is frequently the precursor to a
503once the queue limit is hit and IIS starts rejecting new requests outright. - ASP.NET Applications > Request Execution Time — average time per request for the specific application, distinct from the site-wide
time-takenin logs. Rising execution time with flat traffic points to a code-level or dependency slowdown, not a capacity problem. - Process > % Processor Time and Process > Private Bytes, scoped to the relevant
w3wp.exeinstance — distinguishes CPU-bound slowness from memory pressure that's about to trigger a recycle. - Web Service > Total Method Requests/sec — a throughput baseline. Without knowing normal throughput, a queue depth or execution time number has no context; always compare against a known-good baseline, not an absolute threshold.
The practical workflow is to baseline these counters during normal operation, then alert on deviation rather than on fixed thresholds — "requests queued sustained above baseline for five minutes" is a far more reliable signal than an arbitrary fixed number, since normal queue depth varies enormously by application and hardware.
Troubleshooting 500.19 — Configuration Error
500.19 means IIS could not process the request because of an invalid or inaccessible configuration, and it happens before any application code runs. The sc-win32-status field and the detailed error page both surface a Config Error and a Config File path — always start there rather than guessing.
The most common causes, in order of frequency:
- A locked configuration section. Some sections (e.g.,
system.webServer/handlersorsystem.webServer/modules) are locked at the server or site level and cannot be overridden in a lower-levelweb.config. The error message explicitly names the locked section — the fix is either to unlock it viaappcmd unlock config/applicationHost.config, or to remove the offending override from the site'sweb.config. - A referenced module or feature that isn't installed. A
web.configdeployed from a different server (or written for a different IIS feature set) can reference a handler or module — URL Rewrite, for instance — that simply isn't present on this box. Installing the missing role service or feature resolves it; this is common right after moving an app to a new server. - Malformed XML. A stray tag, mismatched quote, or bad character in
web.config— the error page usually gives a line and column number pointing directly at it. - Incorrect application pool identity permissions on the config file itself, producing an access-denied variant of the same status.
Because this fails before the application layer, application-side logging (including most APM tools) never sees the request — W3C logs and the IIS error page are the only sources of truth here.
Troubleshooting 502.3 — Bad Gateway
502.3 is specific to servers running as a reverse proxy — typically via Application Request Routing (ARR) or URL Rewrite configured with a reverse-proxy rule — and it means the request reached IIS successfully but the upstream server IIS was proxying to did not respond correctly, or at all. This is fundamentally different from 500.19: the local IIS configuration is fine, the problem is downstream.
Sub-status variants clarify the specific failure: a common one is 502.3 Bad Gateway (ARR) shown with a Win32 error like connection refused or timeout. Work through these causes:
- The backend service or app pool is down. If IIS is proxying to another IIS site, another app pool, or an external service (a Node process, a container, an internal API), verify that target is actually running and listening on the expected port.
- The backend is slow enough to exceed ARR's proxy timeout. Check the Server Farm > Proxy Settings in ARR for the configured
Time-outvalue; a backend that eventually responds but too slowly produces the same error as one that never responds. Compare against the backend's owntime-takenif it's also an IIS instance with its own logs. - Network path or firewall blocking the proxy-to-backend hop — especially after backend servers are moved, re-IP'd, or placed behind a new segment. Test connectivity directly from the proxy server to the backend host and port, independent of IIS.
- The server farm's health test is marking the backend unhealthy and ARR is refusing to route to it even though the backend itself is fine — check the farm's health test configuration and current member status in IIS Manager.
Because the failure is on the network hop between proxy and backend, the proxy server's own W3C log will show the 502 with essentially no useful detail beyond the sub-status — the real diagnostic data lives on or near the backend.
Troubleshooting 503 — Service Unavailable
503 means IIS itself is up and listening, but the application pool serving the request is not — most commonly because it has stopped. Unlike 500.19 and 502.3, this is rarely a configuration content problem; it's a process-state problem, and the investigation starts in the Application pools view in IIS Manager and the event logs, not in web.config.
Work the causes in this order:
- Check the app pool state directly. If it shows Stopped, that's the immediate cause; restarting it restores service, but restarting without finding out why it stopped just delays the recurrence.
- Check rapid-fail protection. IIS stops an app pool automatically if it crashes a configured number of times within a configured interval (default: 5 failures in 5 minutes) — this is a deliberate circuit breaker, not a bug. The setting lives under the app pool's Advanced Settings > Rapid-Fail Protection. Repeated worker-process crashes triggering this are the single most common real-world cause of unexplained
503s in production. - Read the Application and System event logs on the IIS server for the time window before the pool stopped. Look for
WAS(Windows Process Activation Service) events reporting the worker process failed to start or terminated unexpectedly, and any application-level exceptions (unhandled exceptions,OutOfMemoryException, a missing dependency at startup) that would explain the crash loop feeding rapid-fail protection. - Check identity and permissions if the pool fails to start at all rather than crashing after starting — an application pool identity that's locked out, had its password expire, or lacks permission to a resource it needs at startup will prevent the pool from coming up, producing a persistent
503until corrected.
Once the pool is confirmed running again, correlate its start time against the W3C logs and, if the crash was intermittent, retroactively enable a FREB rule on time-taken or status code so the next crash produces a full request trace instead of just an event log entry.
Key Takeaways
- Enable W3C logging with the full field set before an incident happens — you cannot retroactively log a request that already occurred
- Use FREB, not the browser or Fiddler, when an error is intermittent or only happens under load
- Watch Requests Queued and Current Connections as leading indicators of saturation before users start reporting timeouts
- Distinguish 500.19 (bad config, XML-level) from 502.3 (bad upstream, proxy-level) from 503 (dead app pool, process-level) — the fix lives in a different layer for each
- Check the rapid-fail protection settings and the Application/System event logs before touching any config file when a pool won't stay running