Start Debugging

Solución: "413 Request Entity Too Large" al subir un archivo a un endpoint de ASP.NET Core

Kestrel limita el cuerpo de las solicitudes a 30.000.000 de bytes por defecto y devuelve 413 cuando lo superas. Sube MaxRequestBodySize globalmente, por endpoint o en web.config detrás de IIS.

Una subida devuelve 413 Request Entity Too Large porque Kestrel limita cada cuerpo de solicitud a 30.000.000 de bytes (unos 28,6 MiB) por defecto, y tu archivo es más grande. Sube el límite donde realmente aplica: asigna MaxRequestBodySize en Kestrel de forma global, aplica [RequestSizeLimit] o [DisableRequestSizeLimit] en una acción de controlador, aumenta FormOptions.MultipartBodyLengthLimit para los envíos de formularios y, detrás de IIS, sube maxAllowedContentLength en web.config. Verificado con ASP.NET Core 11 en .NET 11 con C# 14; los límites y valores por defecto son idénticos de .NET 8 a .NET 11.

El error en contexto

Hay dos formas en que aparece. La primera es la respuesta HTTP que ve el cliente:

HTTP/1.1 413 Request Entity Too Large
Content-Length: 0
Connection: close
Date: Fri, 17 Jul 2026 10:04:11 GMT

La segunda es la línea de registro que Kestrel escribe en el servidor, que es la que te dice exactamente qué límite se disparó:

Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException:
Request body too large. The max request body size is 30000000 bytes.

Ese 30000000 es la pista. Si no has cambiado nada, el número del mensaje es el valor por defecto del framework, y la solución es subir o eliminar el límite en la capa que lo controla. Si el número es otro, alguien ya configuró un límite y lo estás alcanzando. En cualquier caso la causa es la misma: el cuerpo de la solicitud superó un máximo configurado, y el servidor rechazó la conexión antes de que tu handler pudiera terminar de leerlo.

Por qué ocurre

Una subida puede ser bloqueada por cuatro límites distintos, y viven en lugares distintos. Acertar con la solución significa saber cuál produjo tu 413.

Si la solicitud funciona directamente contra Kestrel pero falla detrás de un proxy o de IIS, el límite que necesitas cambiar no está en tu código C#. Revisa primero la capa más externa.

Reproducción mínima

El endpoint más pequeño que reproduce el 413 por defecto. Sin límites personalizados, solo los valores por defecto del framework:

// .NET 11, ASP.NET Core 11, C# 14 -- Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/upload", async (HttpRequest request) =>
{
    // Reading the body is what trips the limit
    using var reader = new StreamReader(request.Body);
    var content = await reader.ReadToEndAsync();
    return Results.Ok(new { bytes = content.Length });
});

app.Run();

Envía cualquier cosa mayor de 30.000.000 de bytes y la lectura lanza la excepción:

# .NET 11 -- generate a 40 MB file and POST it
head -c 40000000 /dev/urandom > big.bin
curl -i -X POST http://localhost:5000/upload \
  -H "Content-Type: application/octet-stream" \
  --data-binary @big.bin
# -> HTTP/1.1 413 Request Entity Too Large

La carga de 40 MB pasa el límite multipart de 128 MiB (de todos modos no es multipart), pero supera el tope de 30 MB del cuerpo de Kestrel, así que obtienes 413, no 400.

Sube el límite donde aplica

Recórrelos según de dónde venga tu 413. La mayoría de las aplicaciones Kestrel autoalojadas solo necesitan el primero.

1. Sube o elimina el límite global de Kestrel

Configura KestrelServerOptions.Limits.MaxRequestBodySize al inicio. Asígnale un número concreto de bytes, o null para eliminar el tope por completo:

// .NET 11, ASP.NET Core 11, C# 14 -- Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    // 200 MB ceiling for the whole app
    options.Limits.MaxRequestBodySize = 200 * 1024 * 1024;
});

var app = builder.Build();

Prefiere un tope real antes que null. Un tamaño de cuerpo sin límite es un vector de denegación de servicio: un solo cliente puede transmitir gigabytes a tu proceso y agotar la memoria o el disco. Elige la subida legítima más grande que esperes y añade margen, en vez de desactivar la protección.

2. Fija un límite por endpoint en controladores con atributos

Si solo un endpoint acepta subidas grandes, no subas el límite global de toda la aplicación. En una acción de controlador o una Razor Page, usa [RequestSizeLimit(bytes)] para fijar un tope concreto, o [DisableRequestSizeLimit] para eliminarlo solo en esa acción. Ambos atributos implementan IRequestSizeLimitMetadata, que el pipeline de MVC lee en cada solicitud:

// .NET 11, ASP.NET Core 11, C# 14
[ApiController]
[Route("api/[controller]")]
public sealed class FilesController : ControllerBase
{
    [HttpPost("upload")]
    [RequestSizeLimit(200 * 1024 * 1024)]   // 200 MB, this action only
    public async Task<IResult> Upload(IFormFile file)
    {
        await using var stream = System.IO.File.Create(
            Path.Combine("uploads", file.FileName));
        await file.CopyToAsync(stream);
        return Results.Ok(new { file.FileName, file.Length });
    }
}

Cambia [RequestSizeLimit(...)] por [DisableRequestSizeLimit] cuando la acción deba aceptar cuerpos arbitrariamente grandes y tengas otra protección (streaming, una comprobación de cuota) en su lugar.

3. Fija el límite por solicitud en las minimal APIs

Las minimal APIs no respetan [RequestSizeLimit] como lo hacen los controladores, porque no hay ningún resource filter de MVC en el pipeline que lo lea. Fija el límite de forma imperativa mediante IHttpMaxRequestBodySizeFeature, antes de que nada lea el cuerpo:

// .NET 11, ASP.NET Core 11, C# 14
app.MapPost("/upload", async (HttpContext context) =>
{
    var sizeFeature = context.Features.Get<IHttpMaxRequestBodySizeFeature>();
    if (sizeFeature is not null && !sizeFeature.IsReadOnly)
    {
        sizeFeature.MaxRequestBodySize = 200 * 1024 * 1024;   // 200 MB
    }

    using var reader = new StreamReader(context.Request.Body);
    var content = await reader.ReadToEndAsync();
    return Results.Ok(new { bytes = content.Length });
});

Comprueba IsReadOnly primero. Una vez que el servidor ha empezado a leer el cuerpo, el límite queda bloqueado y asignarle un valor lanza InvalidOperationException. Por eso también esta técnica no funciona con un parámetro IFormFile en una minimal API: el model binding lee el cuerpo multipart antes de que se ejecute tu handler, así que cuando puedes tocar la feature ya es de solo lectura (esta es la raíz de la incidencia dotnet/aspnetcore #50871, donde las subidas multipart grandes a una minimal API devolvían 200 en silencio con un archivo truncado). Para subidas en minimal API, enlaza HttpContext o HttpRequest, sube el límite y luego lee el formulario tú mismo con request.ReadFormAsync(), o fija un límite para todo un grupo con un pequeño middleware sobre un MapGroup.

4. Sube el límite del formulario multipart para envíos de formularios

Si tu 413 es en realidad un 400 con una InvalidDataException sobre la longitud de un cuerpo multipart, el límite que se disparó es FormOptions.MultipartBodyLengthLimit, no el de Kestrel. Súbelo globalmente mediante options, o por acción de controlador con [RequestFormLimits]:

// .NET 11, ASP.NET Core 11, C# 14 -- global, in Program.cs
builder.Services.Configure<FormOptions>(options =>
{
    options.MultipartBodyLengthLimit = 200 * 1024 * 1024;   // 200 MB
});
// .NET 11, ASP.NET Core 11, C# 14 -- per action
[HttpPost("upload")]
[RequestSizeLimit(200 * 1024 * 1024)]
[RequestFormLimits(MultipartBodyLengthLimit = 200 * 1024 * 1024)]
public async Task<IResult> Upload(IFormFile file) { /* ... */ }

Para una subida grande normalmente necesitas subir ambos: MaxRequestBodySize gobierna toda la solicitud, y MultipartBodyLengthLimit gobierna el cuerpo del formulario dentro de ella. Asígnales el mismo valor.

5. Detrás de IIS, sube maxAllowedContentLength en web.config

Cuando alojas detrás de IIS (in-process o out-of-process), el módulo de filtrado de solicitudes de IIS impone maxAllowedContentLength, por defecto 30.000.000 de bytes, antes de que la solicitud llegue a Kestrel. Subir el límite de Kestrel no hace nada hasta que también subes este. Añádelo a web.config:

<!-- web.config -- value is in bytes; 200 MB here -->
<system.webServer>
  <security>
    <requestFiltering>
      <requestLimits maxAllowedContentLength="209715200" />
    </requestFiltering>
  </security>
</system.webServer>

El módulo de hosting in-process tiene un segundo límite, independiente, que puedes fijar en código mediante IISServerOptions:

// .NET 11, ASP.NET Core 11, C# 14 -- in-process IIS hosting
builder.Services.Configure<IISServerOptions>(options =>
{
    options.MaxRequestBodySize = 200 * 1024 * 1024;   // 200 MB
});

Detrás de IIS puedes necesitar alinear los tres: maxAllowedContentLength en web.config, IISServerOptions.MaxRequestBodySize y el MaxRequestBodySize de Kestrel. La solicitud se rechaza según el que sea más pequeño.

6. Sube el tope en tu proxy inverso

Si hay un proxy delante de la aplicación, tiene su propio límite y devuelve 413 antes de que tu aplicación intervenga. En nginx, la directiva es client_max_body_size (por defecto 1 MiB, mucho más pequeño que el valor por defecto de Kestrel y una sorpresa habitual):

# nginx.conf -- allow 200 MB uploads through the proxy
client_max_body_size 200M;

Para YARP, una API gateway o un balanceador de carga en la nube, busca el ajuste equivalente de tamaño de solicitud en la configuración de esa capa. Subir los límites del lado de la aplicación no ayudará hasta que el proxy deje pasar el cuerpo.

Trampas y variantes

El modelo mental: una subida pasa por una pila de puertas de tamaño (proxy, IIS, tamaño de cuerpo de Kestrel, tamaño de formulario multipart), y la rechaza la primera que supera. Encuentra la capa que produjo tu 413, sube ese límite a un valor real y acotado, y combínalo con streaming para que un límite mayor no se convierta en una responsabilidad mayor.

Relacionado

Fuentes

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Volver