Uploading large files in ASP.NET Core
If you landed here from a 413 Request Entity Too Large, a 404.13, or an upload that dies with no error at all — this page explains every limit in the chain, how to raise each one, and the point where raising limits stops being the right answer.
The chain of limits (any one of them kills the upload)
| Layer | Default | Symptom when hit |
|---|---|---|
Kestrel MaxRequestBodySize | ~28.6 MB (30,000,000 bytes) | 413, or BadHttpRequestException: Request body too large. |
IIS maxAllowedContentLength | ~28.6 MB | HTTP 413 / error 404.13 before your code runs. |
FormOptions.MultipartBodyLengthLimit | 128 MB | InvalidDataException: Multipart body length limit exceeded. |
Reverse proxy (nginx client_max_body_size, etc.) | often 1 MB! | 413 from the proxy; your app never sees the request. |
| Proxy / LB timeouts | 60–300 s | Upload dies mid-flight on slow links; generic network error. |
Raising the limits (the quick fix)
// Kestrel
builder.WebHost.ConfigureKestrel(o => o.Limits.MaxRequestBodySize = 500_000_000);
// Multipart form parsing
builder.Services.Configure<FormOptions>(o => o.MultipartBodyLengthLimit = 500_000_000);
// Or per-endpoint instead of globally:
[RequestSizeLimit(500_000_000)] <!-- IIS: web.config -->
<system.webServer><security><requestFiltering>
<requestLimits maxAllowedContentLength="500000000" />
</requestFiltering></security></system.webServer> This works — up to a point. Every limit you raise is raised for every request, a 2 GB request ties up a connection for its whole life, one dropped packet at 97% restarts from zero, and the proxy timeout is still waiting for you.
The structural fix: stop sending one giant request
Past ~100 MB, the robust pattern is chunked upload: the browser slices the file into 5–10 MB parts, each part is an ordinary small request that fits inside the default limits, failed parts retry individually, and interrupted transfers resume instead of restarting. No global limit changes; no proxy fights.
// Program.cs — endpoints included
builder.Services.AddCoreUpload();
app.MapCoreUploadEndpoints(); <core-upload asp-upload-url="/api/upload/upload"
asp-chunked="true" asp-chunk-size="8MB"
asp-chunk-concurrency="3"
asp-persist-state="true"></core-upload> For multi-gigabyte files whose destination is cloud storage, go one further and send parts straight to S3/Azure/GCS so your web server never carries the bytes at all.
Rules of thumb
- ≤ 30 MB: plain multipart POST, defaults are fine.
- 30–100 MB: raise the limits, keep it simple — or chunk if the audience is on poor networks.
- > 100 MB: chunk. Retries and resume stop being nice-to-haves.
- > 1 GB or cloud-bound: chunk + direct-to-cloud + resumable session.
Weighing this against plain IFormFile model binding? The buffering threshold and where each limit bites are compared side by side in CoreUpload vs IFormFile.