Large uploads and long requests: 413/504 across IIS, Nginx and the app
Size body limits, timeouts and buffering at every hop, and choose streaming over a 30-minute HTTP request.
- Build
- 26100.33650
- Edition
- Standard / Datacenter
- Architecture
- x64
- Support
- Documentation (E1)
- Evidence
- E1Official documentation
- Verified
- 2026-10-29
- Page type
- Task
The short answer
Section titled “The short answer”A large upload crosses several limits:
| Hop | Limit |
|---|---|
| Browser/app | its own max body |
| IIS | maxAllowedContentLength (bytes) |
| Nginx | client_max_body_size |
| Proxy | ARR/proxy body limit |
| App | framework body limit |
| Time | HTTP.sys requestTimeout, Nginx client_body_timeout, proxy read timeout |
Raise the smallest one first; do not set everything to unlimited.
<system.webServer> <security> <requestFiltering> <requestLimits maxAllowedContentLength="524288000" /> </requestFiltering> </security></system.webServer>524288000 = 500MB. Also check the handler/app limit (ASP.NET maxRequestLength, ANCM request body limit) and serverRuntime requestTimeout.
For uploads, uploadReadAheadSize can matter with client certificates/large headers; do not raise it blindly.
client_max_body_size 500m;client_body_timeout 300s;proxy_read_timeout 300s;proxy_send_timeout 300s;client_body_temp_path needs disk space. For large streaming uploads, proxy_request_buffering off forwards bytes to the app instead of buffering the whole body on disk (tradeoff: the app must handle slow clients).
Set the framework’s limit too:
- ASP.NET Core:
RequestSizeLimit/KestrelMaxRequestBodySize; - Express:
express.json({ limit: '...' })/ multer limits; - Flask:
MAX_CONTENT_LENGTH; - Tomcat:
maxPostSize/connector limits.
If the app limit is lower, the proxy passes the body and the app rejects it (often a 413 from the app).
Choose streaming for large files
Section titled “Choose streaming for large files”A 2GB upload or a 30-minute export should not be a single synchronous HTTP request:
- direct-to-object-storage presigned upload;
- chunked/resumable upload;
- background job + status endpoint for exports;
- download via signed URL/stream with a bounded timeout.
Verify
Section titled “Verify”curl.exe -I -H "Content-Length: 600000000" https://lab.example/uploadcurl.exe --limit-rate 100k -T big.bin https://lab.example/uploadCheck the status at each hop and the IIS/Nginx logs. 413 is a body limit; 504 is a timeout; 502 is the upstream.
Lab boundary
Section titled “Lab boundary”E1. The lab did not push a large upload through IIS/Nginx; the limits are the cross-hop checklist for Server 2025.