We still get tickets where a .NET 10 app “allows 100 MB uploads” in code and fails at ~28–30 MB in the browser. The status is not 413 from Kestrel. It is IIS 404.13 — content length too large — and the request never reaches your middleware.

On Windows hosting the stack is two gates: IIS requestFiltering first, then ANCM into the ASP.NET Core app (in-process or out-of-process). App-only limits look correct in Program.cs and still lose to the default maxAllowedContentLength of 30000000 bytes.

#One incident, two knobs

A reseller site raised Kestrel’s MaxRequestBodySize and FormOptions MultipartBodyLengthLimit, republished, and retested. Failed. Failed-request tracing pointed at RequestFilteringModule, not the managed pipeline. web.config had no requestLimits override, so IIS kept the 30 MB ceiling while the app advertised 100 MB.

Symptoms to memorize: 404.13 in the IIS log sc-substatus, often with a generic client error page; nothing useful in ASP.NET Core stdout unless the body actually enters the process. If you only watch app logs, you will “fix” the wrong layer.

xml
<system.webServer>
  <security>
    <requestFiltering>
      <!-- bytes; example = 100 MB -->
      <requestLimits maxAllowedContentLength="104857600" />
    </requestFiltering>
  </security>
  <aspNetCore processPath="dotnet" arguments=".\MyApp.dll" hostingModel="inprocess" />
</system.webServer>

Match that number in the app: Kestrel ServerOptions.Limits.MaxRequestBodySize, FormOptions.MultipartBodyLengthLimit, and any endpoint-specific RequestSizeLimit attributes. Use the same byte value everywhere. Mismatched limits waste time — IIS rejects early, or Kestrel rejects after IIS already accepted the body.

#What we refuse to do

Do not “fix” upload failures by disabling requestFiltering or setting absurd multi-gigabyte caps site-wide. That turns a size bug into an easy memory and disk exhaustion path on a shared app pool. Cap to what the feature needs, keep antiviruses and temp-folder cleanup in mind, and document the paired web.config + Kestrel values in the publish notes.

If large bodies are rare, prefer direct-to-object or chunked processing over buffering the full payload in memory. Limits protect the worker process; they are not a substitute for streaming I/O. After you change requestLimits, recycle the app pool once and confirm with a known-size POST so you are not debugging a cached web.config copy.