Kestrel Tuning & Limits
On this page
Production incident
A client starts uploading huge request bodies slowly (or a bot does it intentionally). Your server accepts the connection and keeps it open. Connections pile up, memory and sockets rise, and legitimate traffic gets starved. Another incident: a partner sends gigantic headers and blows your reverse proxy and Kestrel limits inconsistently, causing 400/431 storms. Kestrel defaults are not "secure by default" for your workload. You must set limits.
Symptoms
- Connection count rises and does not fall.
- Requests hang at the start (headers/body not fully received).
- HTTP 431/413/400 spikes when limits are inconsistent across layers.
- Throughput collapses while CPU remains moderate.
Root causes
- No request size limits; slow clients consume resources.
- Keep-alive and timeouts not tuned; idle connections stay forever.
- Header limits not aligned with proxy/gateway limits.
- Unlimited HTTP/2 streams or connection reuse patterns not understood.
Diagnosis
# Look for Kestrel configuration grep -R "ConfigureKestrel" -n . grep -R "KestrelServerOptions" -n . grep -R "MaxRequestBodySize\|RequestHeadersTimeout\|KeepAliveTimeout" -n .
Check the full chain: CDN/WAF, reverse proxy (nginx/ingress), gateway, and Kestrel. If limits differ, clients will see random failures.
Anti-pattern
- Relying on defaults for a public API with upload endpoints.
- Setting extremely low limits globally and breaking legitimate clients.
- Setting limits only at the proxy and not at Kestrel (defense in depth matters).
Correct pattern
Set sane global defaults and override per endpoint when needed (uploads, streaming). Align limits across layers.
// Program.cs
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(10);
options.Limits.KeepAliveTimeout = TimeSpan.FromSeconds(30);
options.Limits.MaxRequestBodySize = 10 * 1024 * 1024; // 10 MB default
options.Limits.MaxRequestHeadersTotalSize = 32 * 1024; // 32 KB
options.Limits.MaxRequestHeaderCount = 100;
});
Endpoint overrides
// Allow larger body on a specific upload endpoint
app.MapPost("/upload", async (HttpContext ctx) =>
{
// streaming processing goes here
}).WithMetadata(new RequestSizeLimitAttribute(200 * 1024 * 1024)); // 200MB
Performance and abuse controls
- Prefer streaming uploads and downloads. Do not buffer full bodies in memory.
- Use server-side rate limiting for expensive endpoints.
- Set request body size limits and header limits as a hard boundary.
Security impact
- Limits reduce DOS risk (slowloris, oversized headers, oversized bodies).
- Aligning limits avoids weird bypasses between proxy and app.
Operational notes
- Monitoring: connections, request queueing, 413/431 rates, request aborted counts.
- Rollout: tighten limits gradually and watch error rates by client. Communicate breaking changes to partners.
- Rollback: if a limit change breaks a key client, relax per-route first, not globally.
Checklist
- Request body and header limits are explicitly configured.
- Timeouts (headers, keep-alive) are set to avoid idle resource pinning.
- Limits are aligned across CDN/proxy/gateway/Kestrel.
- Uploads/downloads use streaming, not buffering.
- 413/431/aborted request metrics are monitored.