Kestrel:Limits

Know Kestrel's default request limits, set them in code or a bound Kestrel:Limits section, and raise one limit per endpoint without opening the whole server.

Signature
builder.WebHost.ConfigureKestrel(o => o.Limits.MaxRequestBodySize = 10 * 1024 * 1024);  |  Kestrel:Limits:<Name>
  • Added in .NET ASP.NET Core 2.0

Parameters

ParameterTypeDescription
$MaxRequestBodySizeoptionallong?= 30000000 (about 28.6 MB)Largest request body in bytes; null means unlimited. Exceeding it gives 413.
$MaxRequestHeadersTotalSizeoptionalint= 32768All request headers together, in bytes. Exceeding it gives 431.
$MaxRequestHeaderCountoptionalint= 100Number of request headers. Exceeding it gives 431.
$MaxRequestLineSizeoptionalint= 8192Request line (method, path, query, version) in bytes. Exceeding it gives 414.
$KeepAliveTimeoutoptionalTimeSpan= 00:02:10Idle time before an idle keep-alive connection is closed.
$RequestHeadersTimeoutoptionalTimeSpan= 00:00:30Time allowed to receive all request headers.
$MinRequestBodyDataRateoptionalMinDataRate?= 240 bytes/s after 5 sMinimum upload rate after a grace period; slower uploads are aborted.
$MaxConcurrentConnectionsoptionallong?= null (unlimited)Open connections per server; extra connections are accepted, then closed immediately and logged as rejected.
$Http2.MaxStreamsPerConnectionoptionalint= 100Concurrent HTTP/2 streams per connection.

Return value

KestrelServerLimitsServer-wide limits Kestrel enforces on every connection and request before your code runs, or while your code reads the body.

Errors

Nothing at startup. At request time: 413 for the body, 431 for headers, 414 for the request line, 408 or an aborted connection for timeouts and data rates. Reading an oversized body throws BadHttpRequestException inside the endpoint.

Kestrel checks limits while it parses: header size and count, request line length, header and keep-alive timeouts, minimum data rates, and the body size as your code reads it. They protect the process from slow-loris clients, oversized headers and huge uploads before any middleware runs. The defaults are sensible for JSON APIs; the decision a team makes is where to raise them, and the answer is almost always "on one endpoint, not the server".

The defaults

LimitDefaultStatus when exceeded
MaxRequestBodySize30,000,000 bytes413
MaxRequestHeadersTotalSize / MaxRequestHeaderCount32 KB / 100431
MaxRequestLineSize8 KB414
RequestHeadersTimeout / KeepAliveTimeout30 s / 130 sConnection closed
MinRequestBodyDataRate240 bytes/s after a 5 s graceRequest aborted
MaxConcurrentConnectionsUnlimitedConnection accepted then closed immediately (logged as rejected; the client sees a reset)
Http2.MaxStreamsPerConnection100Streams queue at the client

Kestrel:Limits is not bound by default

The default builders read Kestrel:Endpoints, Kestrel:EndpointDefaults and Kestrel:Certificates, but in .NET 10 a Kestrel:Limits section (or Kestrel__Limits__MaxRequestBodySize in the environment) has no effect until you bind it: builder.Services.Configure<KestrelServerOptions>(builder.Configuration.GetSection("Kestrel")). Without that line the setting looks applied in review and does nothing in production. Setting limits in ConfigureKestrel code is the other reliable way.

Per-endpoint overrides

Keep the server limit small and raise it where needed: [RequestSizeLimit(bytes)] or [DisableRequestSizeLimit] on controllers and, as endpoint metadata, on minimal APIs (.WithMetadata(new RequestSizeLimitAttribute(...))), or IHttpMaxRequestBodySizeFeature in code before the body is read. A form or IFormFile upload also has FormOptions.MultipartBodyLengthLimit (about 128 MB) to consider.

Production failure modes: an upload endpoint fails at 28.6 MB with a 413 that never reaches your logs as an exception; a proxy in front has its own lower limit (nginx's client_max_body_size defaults to 1 MB) and answers 413 before Kestrel sees anything; and a team that sets MaxRequestBodySize = null server-wide lets a single client stream gigabytes into a JSON endpoint that buffers it in memory. Large tokens or cookies hit the 32 KB header limit with 431, usually after a login change adds group claims to a cookie.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Allow 200 MB product image uploads on one endpoint while JSON endpoints keep a 1 MB body limit
  • Fix 431 errors after a sign-in change pushed group claims into a cookie larger than the 32 KB header limit
  • Find why Kestrel__Limits__MaxRequestBodySize in a Helm chart had no effect: the Kestrel section was never bound

Frequently asked questions

What is Kestrel's default maximum request body size?

30,000,000 bytes, about 28.6 MB. Larger bodies get 413 Payload Too Large; a proxy in front may have a smaller limit of its own.

Why does Kestrel:Limits in appsettings.json not work?

The default builders do not bind the Limits part of the Kestrel section. Add builder.Services.Configure<KestrelServerOptions>(builder.Configuration.GetSection("Kestrel")) or set the limits in ConfigureKestrel.

How do I raise the limit for one minimal API endpoint?

Add endpoint metadata: .WithMetadata(new RequestSizeLimitAttribute(bytes)) or new DisableRequestSizeLimitAttribute(). In a handler you can also set IHttpMaxRequestBodySizeFeature.MaxRequestBodySize before reading the body.

More examples

appsettings.json limits plus the binding they need

Binding the whole <code>Kestrel</code> section is harmless for <code>Endpoints</code>, which the endpoint loader reads separately. The comment states the dependency so nobody deletes the binding line thinking the JSON is enough. 5 MB suits a JSON API; uploads get their own per-endpoint limit.

Example · json
{
  // Only applies with builder.Services.Configure<KestrelServerOptions>(builder.Configuration.GetSection("Kestrel"))
  "Kestrel": {
    "Limits": {
      "MaxRequestBodySize": 5242880,
      "MaxRequestHeadersTotalSize": 65536,
      "KeepAliveTimeout": "00:01:00",
      "RequestHeadersTimeout": "00:00:15",
      "Http2": {
        "MaxStreamsPerConnection": 200
      }
    },
    "Endpoints": {
      "Http": {
        "Url": "http://*:8080"
      }
    }
  }
}

Upload endpoint with its own limits

The server allows 1 MB, the image endpoint 200 MB, and both Kestrel and <code>FormOptions</code> must agree because multipart parsing has its own limit. The slower minimum data rate keeps mobile uploads from being aborted while still cutting off stalled connections. A 200 MB endpoint requires authorization, constrains <code>sku</code> and keeps it out of the file path. <code>DisableAntiforgery</code> is acceptable only because callers send a bearer token; in a cookie-authenticated app keep antiforgery on for form uploads.

Example · csharp
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(kestrel =>
{
    kestrel.Limits.MaxRequestBodySize = 1 * 1024 * 1024;
    kestrel.Limits.MinRequestBodyDataRate = new(bytesPerSecond: 1024, gracePeriod: TimeSpan.FromSeconds(10));
});
builder.Services.Configure<FormOptions>(form => form.MultipartBodyLengthLimit = 200 * 1024 * 1024);
builder.Services.AddAuthentication().AddBearerToken();
builder.Services.AddAuthorization();

var app = builder.Build();

// Route regex braces are doubled in route templates.
app.MapPost("/products/{sku:regex(^[A-Z0-9-]{{1,32}}$)}/images", async (string sku, IFormFile image, CancellationToken ct) =>
{
    // The file name never contains user input.
    var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.bin");
    await using var target = File.Create(path);
    await image.CopyToAsync(target, ct);
    return TypedResults.Ok(new { sku, bytes = image.Length });
})
.WithMetadata(new RequestSizeLimitAttribute(200 * 1024 * 1024))
.RequireAuthorization()
.DisableAntiforgery();

app.Run();

The proxy in front needs the same number

ingress-nginx rejects bodies over 1 MB by default with its own 413 page, before Kestrel sees the request. Raising the Kestrel limit without this annotation changes nothing for clients, and the 413 never shows up in the application's logs.

Example · yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: catalog-api
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "200m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
spec:
  ingressClassName: nginx
  rules:
    - host: catalog.shop.example.com
      http:
        paths:
          - path: /products
            pathType: Prefix
            backend:
              service:
                name: catalog-api
                port:
                  number: 80

Discussion

  • Be the first to comment on this lesson.