⚙️ The Upload That Failed Only on the Slowest Connections
Kestrel enforces a minimum data rate on both reading a request and writing a response, dropping the connection if a client sends or receives data more slowly than the configured floor for more than a short grace period – a safety feature meant to free up server resources from a client that has effectively stalled. A large file upload from a genuinely slow or congested connection can trip that same floor well before the upload would have otherwise finished, aborting a transfer that a generous request body size limit and a generous request timeout both would have allowed to complete. The failure looks exactly like a generic connection reset to the client, with nothing in the client-visible error pointing at the server setting that actually caused it.
🔎 The Problem
// A generous size limit and request timeout both suggest this
// upload should be allowed to take its time:
builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = 500_000_000;
});
builder.Services.Configure<KestrelServerOptions>(options =>
{
options.Limits.MaxRequestBodySize = 500_000_000;
});
// But Kestrel's default minimum data rate still applies underneath
// both of those settings - a client sustaining less than roughly
// 240 bytes/second for more than a short grace period gets its
// connection aborted mid-upload, regardless of the body size limit
// or how generous any other timeout was configured to be:
// MinRequestBodyDataRate: 240 bytes/sec, 5 second grace period
// (the actual Kestrel default, unless explicitly overridden)
✅ Fix: Set the Minimum Data Rate to Match What Slow Uploads Actually Need
- Override MinRequestBodyDataRate explicitly for the specific upload endpoint, lowering the bytes-per-second floor or disabling the check entirely for that route rather than for the whole application, once you know real clients upload from genuinely slow connections.
- Reproduce the failure by actually throttling a test upload’s bandwidth to a realistic worst case, instead of relying on a fast local network connection where the minimum data rate floor is never remotely close to being tripped.
- Log the specific exception Kestrel raises when it aborts a connection for falling below the minimum rate, since it is distinct from a generic timeout and immediately points at the correct setting instead of leaving the cause to be guessed at from a plain connection reset.
⚠️ Why This Is Easy to Miss
- Every other timeout and size limit involved in the upload path can be configured generously and still not matter, because the minimum data rate check operates independently of all of them and enforces its own floor regardless of what the rest of the configuration allows.
- The failure only reproduces on a connection slow enough to fall under the floor for long enough to trigger it, so testing on a typical office or home connection almost never reveals the problem – it surfaces only once real users on weak mobile or rural connections try the same upload.
A generous size limit tells Kestrel how much data it will accept – the minimum data rate tells it how slowly it will accept that data, and the two settings answer completely different questions.
