# 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](https://www.patreon.com/CodingDroplets), 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](https://aspnetcoreapi.codingdroplets.com/) alongside authentication, validation, and error handling — all wired together in a single, runnable codebase.

[![ASP.NET Core Web API: Zero to Production](https://newsletter.codingdroplets.com/images/aspnet-core-api-course-banner-1.jpg align="center")](https://aspnetcoreapi.codingdroplets.com/)

## 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:

```csharp
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`:

```xml
<system.webServer>
  <security>
    <requestFiltering>
      <requestLimits maxAllowedContentLength="104857600" /> <!-- 100 MB in bytes -->
    </requestFiltering>
  </security>
</system.webServer>
```

You can also configure this programmatically via the `IISServerOptions`:

```csharp
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:

```nginx
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:

```csharp
[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:

```csharp
builder.Services.Configure<FormOptions>(options =>
{
    options.MultipartBodyLengthLimit = 209_715_200; // 200 MB
});
```

Or per-controller/action using `[RequestFormLimits]`:

```csharp
[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`:

```apache
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](https://serilog.net/) 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 `MaxRequestBodySize` at 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](https://github.com/codingdroplets/dotnet-file-upload-download-api) 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.
