413 Request Entity Too Large in ASP.NET Core: Causes and Fixes
Getting a 413 Request Entity Too Large error in your ASP.NET Core API can be maddening — you raise the limit in one place, reload the app, and the error is still there. That is because this error does not come from a single source. It can originate from Kestrel, from IIS, from a reverse proxy like Nginx or YARP, or from an attribute on a specific endpoint. Understanding exactly which layer is rejecting the request is the only way to fix it reliably. The complete, production-ready implementation for handling large uploads — including streaming, cancellation, and per-endpoint constraints — is available on Patreon, with source code that maps directly to what enterprise teams actually ship.
If you want to see how file uploads fit into a complete production API, the file upload and download patterns are covered in the Zero to Production course alongside authentication, validation, and error handling — all wired together in a single, runnable codebase.
What Does HTTP 413 Actually Mean?
HTTP 413 means the server refused to process the request because the request body was larger than the server is configured to accept. The response can come from any hop in the request chain — the reverse proxy, the web server, or the application itself. Each hop has its own limit, and they must all agree before a large request can reach your controller.
The key diagnostic question is: where is the 413 coming from? Check your response headers. If the Server header says nginx, the request was rejected before reaching ASP.NET Core. If the header says Microsoft-IIS, IIS rejected it. If neither, Kestrel or the ASP.NET Core middleware rejected it.
Common Causes of 413 in ASP.NET Core
Cause 1: Kestrel's Default MaxRequestBodySize
Kestrel, the default .NET web server, imposes a default maximum request body size of 30 MB (30,000,000 bytes). Any request body that exceeds this limit is rejected with a 413 before it reaches your endpoint logic.
This is the most common cause when running ASP.NET Core without IIS or a reverse proxy in front — for example, in Docker containers or when hosted directly.
Fix: Configure MaxRequestBodySize on the Kestrel options at the host level:
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.MaxRequestBodySize = 100_000_000; // 100 MB
});
Setting this to null removes the limit entirely, which is not recommended for public-facing APIs.
Cause 2: IIS maxAllowedContentLength
When hosting behind IIS using AspNetCoreModuleV2, IIS itself inspects the request before forwarding it to the .NET process. IIS enforces a default maxAllowedContentLength of 30 MB via its Request Filtering module.
Fix: Add or update this in web.config:
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="104857600" /> <!-- 100 MB in bytes -->
</requestFiltering>
</security>
</system.webServer>
You can also configure this programmatically via the IISServerOptions:
builder.Services.Configure<IISServerOptions>(options =>
{
options.MaxRequestBodySize = 100_000_000;
});
Both settings must align. Setting one without the other will still result in a 413.
Cause 3: Nginx client_max_body_size
Nginx has its own hard limit on incoming request bodies, controlled by the client_max_body_size directive. The default is 1 MB. This is the single most common reason developers raise the Kestrel limit, reload their app, and still see 413 — the error was never coming from .NET in the first place.
Fix: In your Nginx server or location block:
server {
client_max_body_size 100M;
...
}
After changing the Nginx config, reload (not just restart) Nginx so active connections are not dropped: nginx -s reload.
Cause 4: The [RequestSizeLimit] Attribute
ASP.NET Core provides a [RequestSizeLimit] attribute for per-endpoint overrides. If an endpoint or controller has this attribute with a value lower than the request body, the request is rejected at that endpoint — regardless of the global Kestrel or IIS settings.
This is often added defensively as a security measure on most endpoints, then forgotten when a specific endpoint genuinely needs a higher limit.
Fix: Apply the attribute explicitly on the upload endpoint with the appropriate value:
[HttpPost("upload")]
[RequestSizeLimit(100_000_000)] // 100 MB for this endpoint only
public async Task<IActionResult> Upload(IFormFile file)
{
// ...
}
To disable the request size limit entirely for a specific action (useful during development or for trusted internal endpoints), use [DisableRequestSizeLimit].
Cause 5: Multipart Body Size Limits in Form Handling
Even when the raw request body size is within limits, ASP.NET Core's multipart form body parsing has its own constraints. The FormOptions class governs limits like MultipartBodyLengthLimit (default: 134,217,728 bytes / 128 MB) and MultipartHeadersLengthLimit.
These can be configured globally:
builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = 209_715_200; // 200 MB
});
Or per-controller/action using [RequestFormLimits]:
[RequestFormLimits(MultipartBodyLengthLimit = 209_715_200)]
[RequestSizeLimit(209_715_200)]
public async Task<IActionResult> Upload(IFormFile file)
Cause 6: Apache LimitRequestBody
For teams hosting behind Apache rather than Nginx, the equivalent directive is LimitRequestBody. The default is 0 (unlimited in Apache itself), but many hardened configurations set a smaller value.
Fix: In your Apache VirtualHost or .htaccess:
LimitRequestBody 104857600
Cause 7: YARP or Other Reverse Proxy Request Limits
Teams using YARP (Yet Another Reverse Proxy), Caddy, or HAProxy as an internal gateway may have request body limits configured at that layer too. Check your reverse proxy documentation for the equivalent of client_max_body_size.
How to Diagnose Which Layer Is Sending the 413
Rather than guessing, use these diagnostic steps:
Step 1 — Check the response headers. The Server header identifies which component responded. nginx/1.x.x points to Nginx, Microsoft-IIS/10.0 points to IIS, Kestrel or absence of a Server header suggests the ASP.NET Core middleware pipeline.
Step 2 — Test with a small payload. Send a request under 1 MB. If it succeeds, the limit is somewhere between 1 MB and your target. If it also fails, the 413 is routing-related or tied to a different constraint.
Step 3 — Bypass the reverse proxy. If your service is accessible directly (via localhost on the server, or via a NodePort in Kubernetes), send the request directly to the Kestrel port. If it now succeeds, the limit is in the proxy layer, not in .NET.
Step 4 — Enable structured logging. Add Serilog with request logging enabled (UseSerilogRequestLogging()), then inspect the request log for the endpoint that triggered the 413. If the request never appears in the .NET logs, it was rejected upstream.
Step 5 — Check for [RequestSizeLimit] on the action. Search your codebase for RequestSizeLimit and DisableRequestSizeLimit attributes. One may have been added to a base controller class and inherited by accident.
Is It Safe to Disable All Limits?
Not on public-facing endpoints. Request body size limits exist for a reason — they protect your service from resource exhaustion attacks where a client sends a multi-gigabyte body to consume memory, CPU, or disk.
A better approach is to apply the principle of least privilege:
- Keep
MaxRequestBodySizeat a sensible global level (30–50 MB is reasonable for most APIs) - Use
[RequestSizeLimit]to raise the limit only on specific upload endpoints that genuinely need it - Use
[DisableRequestSizeLimit]only on internal, authenticated endpoints where the caller is trusted - For very large uploads (hundreds of MB or more), consider streaming directly to Azure Blob Storage or S3 rather than buffering in memory, to avoid memory pressure entirely
For a full working example including streaming, per-endpoint limits, and error handling, the GitHub repo has a runnable ASP.NET Core file upload and download API you can clone and adapt.
Prevention: Handling Oversized Requests Gracefully
By default, ASP.NET Core throws a BadHttpRequestException or InvalidDataException when a request exceeds the configured limit. If you want to return a meaningful 413 response with a Problem Details body rather than an unhandled exception reaching your global handler, configure the exception handling explicitly in your middleware:
Handle this in your IExceptionHandler (or exception middleware) by catching BadHttpRequestException with status code 413 and mapping it to a Problem Details response — this ensures clients receive a structured JSON error rather than an HTML error page.
On the client side, always check for 413 responses and surface a meaningful message to users ("Your file exceeds the maximum allowed size of X MB") rather than displaying a generic error.
Quick Reference: All Layers and Their Limits
| Layer | Default Limit | How to Change |
|---|---|---|
| Kestrel | 30 MB | KestrelServerLimits.MaxRequestBodySize |
| IIS (Request Filtering) | 30 MB | maxAllowedContentLength in web.config |
| IIS (IISServerOptions) | Inherits from Kestrel | IISServerOptions.MaxRequestBodySize |
| Nginx | 1 MB | client_max_body_size in nginx.conf |
| Apache | Unlimited (default) | LimitRequestBody in httpd.conf |
| FormOptions | 128 MB (multipart) | FormOptions.MultipartBodyLengthLimit |
[RequestSizeLimit] |
Per-action override | Apply directly on the action method |
FAQ
Why am I still getting 413 after setting MaxRequestBodySize in Kestrel?
Because the limit is being enforced by a different layer — most commonly Nginx's client_max_body_size (default: 1 MB) or IIS's maxAllowedContentLength (default: 30 MB). Check your response headers to identify which server is sending the 413, then fix the limit at that layer. All layers in the request chain must be configured consistently.
What is the difference between [RequestSizeLimit] and [DisableRequestSizeLimit]?
[RequestSizeLimit(n)] sets a specific maximum body size for that endpoint, overriding the global Kestrel or IIS limit. [DisableRequestSizeLimit] removes the limit entirely for that endpoint. Use DisableRequestSizeLimit only on trust-boundary endpoints — for example, internal job APIs called by other services behind a secured network, not on public-facing upload endpoints.
Should I set MaxRequestBodySize to null to remove all limits?
In most cases, no. Removing the limit globally exposes your API to resource exhaustion attacks. Instead, keep a reasonable global limit and use [RequestSizeLimit] to raise the ceiling only for specific endpoints that handle large payloads.
How do I return a proper 413 Problem Details response instead of an exception?
Catch BadHttpRequestException in your IExceptionHandler implementation and check for a status code of 413. Map it to a ProblemDetails response with StatusCodes.Status413RequestEntityTooLarge. This requires .NET 8 or later for the IExceptionHandler interface; in earlier versions, use exception middleware.
Does [RequestSizeLimit] apply to JSON request bodies, not just file uploads?
Yes. [RequestSizeLimit] applies to any request body — JSON payloads, form data, and file uploads. If your API receives large JSON documents (for example, bulk import endpoints), you may need to raise the limit for those actions too, not just file upload endpoints.
Can Kestrel's limit be configured per-connection rather than globally?
Yes. Kestrel supports per-connection overrides via IHttpMaxRequestBodySizeFeature in middleware, allowing you to set MaxRequestBodySize dynamically based on the request path, authentication context, or other request properties before the body is read.
Why does 413 sometimes show as the request being cancelled on the client, with no error message?
Some reverse proxies (including Nginx) close the connection immediately when the request body exceeds client_max_body_size, before sending the 413 response. Depending on the client library and timing, this can manifest as a connection reset or timeout rather than a clean 413. This is Nginx's default behaviour; the ASP.NET Core server itself would send a proper 413 response.






