When a page is slow only in production, Application Insights is not always available and Event Viewer rarely shows the pipeline step that burned the time. IIS Failed Request Tracing (FREB) still solves that on Windows Server and shared IIS: it writes a per-request XML trace through HTTP.sys, the native modules, managed handlers, and your ASP.NET or ASP.NET Core pipeline.

The ops mistake is enabling FREB wide open. Unfiltered tracing fills disks and slows the worker process. The useful pattern is a narrow rule—status codes you care about, or a time-taken threshold—scoped to one site or one path, with a small freb.xsl-friendly log folder you can purge on a schedule.

#What FREB actually captures

Failed Request Tracing is not only for failures. Despite the name, you can trigger on time taken, status/substatus, or event severity. Each successful capture is an XML file under the site’s FailedReqLogFiles folder. Open it in Internet Explorer or Edge’s IE mode (the freb.xsl stylesheet still formats cleanly there), or parse it with PowerShell when you need a summary.

For classic ASP.NET on the System.Web pipeline you see AuthenticateRequest, ExecuteRequestHandler, and UpdateRequestCache timings. For ASP.NET Core with the in-process ANCM module on IIS 10.0, you still get useful boundaries: native modules, AspNetCoreModuleV2 handoff, and response start. That is often enough to separate “SQL waited 4 seconds” from “static file middleware never ran” without attaching a debugger on a shared box.

#Enable tracing with a tight rule

On a Windows Server you control, install the Tracing role service if it is missing, then enable it per site. Prefer the IIS Manager UI once to confirm the provider list, then keep the rule in applicationHost.config or a delegated web.config when your host allows it. On many shared Windows plans, FREB is already installed; you only need permission to enable it for your site and a writable log path under your home directory.

A practical starting rule for production: trace 500–504 responses and any request slower than 5 seconds, limited to 50 files so a runaway bot cannot fill the volume overnight.

xml
<tracing>
  <traceFailedRequests>
    <add path="*.aspx">
      <traceAreas>
        <add provider="ASP" verbosity="Verbose" />
        <add provider="ASPNET" areas="Infrastructure,Module,Page,AppServices" verbosity="Verbose" />
        <add provider="ISAPI Extension" verbosity="Verbose" />
        <add provider="WWW Server"
             areas="Authentication,Security,Filter,StaticFile,CGI,Compression,Cache,RequestNotifications,Module,FastCGI,WebSocket,Rewrite,RequestRouting"
             verbosity="Verbose" />
      </traceAreas>
      <failureDefinitions statusCodes="500-504" timeTaken="00:00:05" />
    </add>
  </traceFailedRequests>
</tracing>

For ASP.NET Core sites, change path to "*" or a specific app segment (for example "api/*") and keep the WWW Server plus ASPNETCORE providers your host exposes. Drop verbosity to "Warnings" if you only need failure breadcrumbs. Always set a low max log files count in the site’s Failed Request Tracing settings (16–50) so old traces rotate.

#Read the trace like an ops checklist

Open the XML and jump to the compact view. Sort mental attention in this order:

  • timeTaken on the top-level request versus the first long gap between notifications—that gap is your culprit region.
  • AUTHENTICATE_REQUEST / AUTHORIZE_REQUEST delays (external identity, slow cookie ticket protection, or remote auth).
  • EXECUTE_REQUEST_HANDLER duration (your app, EF Core, or outbound HTTP).
  • Module names you did not expect (URL Rewrite loops, request filtering rejects, custom native modules).
  • Final status and substatus (404.13 upload limit, 500.19 config, 503.2 concurrent limit—not all “500s” are app exceptions).

If the handler section is short but the overall request is long, look before the handler: rewrite maps, failed request filtering, or authentication. If the handler owns almost all of timeTaken, FREB has done its job—move to app logs, SQL DMVs, or a temporary ActivitySource around the suspect dependency. Do not leave Verbose + path="*" running after you finish; switch the rule off or raise timeTaken so only outliers remain.

#Shared-hosting constraints and cleanup

On shared Windows hosting you usually cannot install features, but you can still use FREB when the host enabled the Tracing module and granted Failed Request Tracing configuration at the site level. Point logs at a folder inside your site home (not wwwroot if you can avoid publishing it), deny anonymous HTTP access to that folder, and schedule cleanup. A simple scheduled task or host cron-equivalent that deletes freb*.xml older than two days prevents silent disk pressure next to your content.

powershell
# Run from an elevated session on a VPS, or adapt paths for your home directory
$logRoot = "C:\inetpub\logs\FailedReqLogFiles"
Get-ChildItem $logRoot -Recurse -Filter "fr*.xml" |
  Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-2) } |
  Remove-Item -Force

Correlate wall-clock time with app pool recycles. A burst of traces right after a recycle often means cold start, missing pre-load, or a first-request compilation hit—not a steady-state regression. For .NET 10 apps on in-process IIS, pair a short FREB window with stdout logging turned off unless you are actively debugging startup; double logging during a FREB session is how disks fill on Friday nights.

Practical takeaway: add one Failed Request Tracing rule for 5xx and timeTaken over a few seconds, cap the file count, and read the notification gaps before you change code. When the slow section is identified, disable or loosen the rule so IIS goes back to quiet production behavior. That workflow works the same on a Windows VPS and on disciplined Windows shared hosts where you already live in Web.config, app pools, and log folders rather than a service mesh.