An ASP.NET Core API on Kestrel should set MaxRequestBodySize (ideally per endpoint), RequestHeadersTimeout, KeepAliveTimeout and MaxConcurrentConnections explicitly in ConfigureKestrel. Microsoft documents these as options "especially useful in Internet facing deployments". Writing the values down means a version upgrade cannot silently change your exposure.
Where the settings live
Kestrel limits sit on KestrelServerOptions.Limits, configured in Program.cs through builder.WebHost.ConfigureKestrel. The relevant ones for a backend developer:
| Limit | What it controls |
|---|---|
| MaxRequestBodySize | Maximum size of any request body, in bytes |
| RequestHeadersTimeout | Maximum time the server spends receiving request headers |
| KeepAliveTimeout | How long an idle keep alive connection stays open |
| MaxConcurrentConnections | Maximum number of open connections |
| MinRequestBodyDataRate | Minimum rate at which a client must send body data. See the slow client post |
Defaults to check against your version
The ASP.NET Core 5.0 version of the Kestrel options page lists these defaults: a maximum request body of 30,000,000 bytes (about 28.6 MB), a request headers timeout of 30 seconds, a keep alive timeout of 2 minutes, and an unlimited number of connections (null). The current page for newer versions shows the options and examples but may state values differently, so read the page for the version you run, and prefer to set values yourself.
A 28.6 MB default body is generous for a JSON API. If a login endpoint accepts that much, you are paying memory and CPU for requests that should be rejected after a few kilobytes.
Per endpoint body limits
The documentation recommends the RequestSizeLimit attribute to override the limit in an MVC app, and shows the IHttpMaxRequestBodySizeFeature for middleware. The feature has an IsReadOnly property, and setting the limit after the app has started to read the request throws an exception.
builder.WebHost.ConfigureKestrel(o =>
{
o.Limits.MaxRequestBodySize = 1_000_000; // 1 MB default for the API
o.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(15);
o.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(1);
o.Limits.MaxConcurrentConnections = 1000;
});
[RequestSizeLimit(50_000_000)]
[HttpPost("upload")]
public IActionResult Upload() { /* stream to storage */ }
Set a small default, then raise it only on the route that needs it.
Two things that change the picture
- Behind IIS in the out of process model, the documentation says IIS sets the limit and Kestrel's body size limit is disabled. Check which layer owns the limit in your hosting model.
- Timeouts and data rate limits are not enforced while a debugger is attached to the Kestrel process, as the docs state. Do not test limits with a debugger running.
Steps
- Pick a small global MaxRequestBodySize that fits your normal payloads.
- Raise it with RequestSizeLimit only for upload routes, and stream those bodies.
- Set RequestHeadersTimeout and KeepAliveTimeout explicitly.
- Choose MaxConcurrentConnections from your load tests and instance size.
- Confirm which layer enforces limits if you sit behind a proxy or IIS.
- Test with an oversized body, a slow header client and many idle connections, without a debugger.
Try it yourself
Click through it at your own pace, or open it full screen. See how a live scan exercises a running app from the outside.
Quick check
What happens when a body exceeds MaxRequestBodySize?
The request is rejected by Kestrel before your code reads the rest of it. Return a clear 413 from your own handlers where you apply limits yourself.
Is MaxConcurrentConnections the same as a rate limit?
No. It caps open connections. Rate limiting by client is a separate control. See which ASP.NET Core rate limiter to use.
Safeguard dependency scanning tracks the NuGet packages your API ships, with reachability analysis to separate reachable findings from noise, and the free scan is at gold.safeguard.sh/scan.
Sources
Last reviewed: 19 October 2026.