Start Debugging

Исправление: "413 Request Entity Too Large" при загрузке файла на endpoint ASP.NET Core

Kestrel по умолчанию ограничивает тело запроса 30 000 000 байтами и возвращает 413 при превышении. Увеличьте MaxRequestBodySize глобально, для endpoint или в web.config за IIS.

Загрузка возвращает 413 Request Entity Too Large, потому что Kestrel по умолчанию ограничивает каждое тело запроса 30 000 000 байтами (около 28,6 МиБ), а ваш файл больше. Увеличьте лимит там, где он действительно применяется: задайте MaxRequestBodySize в Kestrel глобально, примените [RequestSizeLimit] или [DisableRequestSizeLimit] на action контроллера, увеличьте FormOptions.MultipartBodyLengthLimit для отправки форм, а за IIS увеличьте maxAllowedContentLength в web.config. Проверено на ASP.NET Core 11 на .NET 11 с C# 14; лимиты и значения по умолчанию идентичны от .NET 8 до .NET 11.

Ошибка в контексте

Она проявляется двумя способами. Первый - это HTTP-ответ, который видит клиент:

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

Второй - это строка журнала, которую Kestrel пишет на сервере и которая точно говорит, какой лимит сработал:

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

Эти 30000000 - подсказка. Если вы ничего не меняли, число в сообщении - это значение фреймворка по умолчанию, и решение в том, чтобы увеличить или убрать лимит на том уровне, которому он принадлежит. Если число другое, значит кто-то уже настроил лимит, и вы в него упираетесь. В любом случае причина одна: тело запроса превысило настроенный максимум, и сервер отклонил соединение, прежде чем ваш handler успел дочитать его.

Почему это происходит

Загрузку могут заблокировать четыре разных лимита, и они находятся в разных местах. Правильно подобрать решение значит понять, какой из них породил ваш 413.

Если запрос работает напрямую против Kestrel, но не работает за прокси или IIS, лимит, который нужно изменить, не в вашем коде на C#. Сначала проверьте самый внешний уровень.

Минимальное воспроизведение

Наименьший endpoint, воспроизводящий 413 по умолчанию. Никаких своих лимитов, только значения фреймворка по умолчанию:

// .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();

Отправьте что-нибудь больше 30 000 000 байт, и чтение бросит исключение:

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

Нагрузка в 40 МБ проходит multipart-лимит в 128 МиБ (она в любом случае не multipart), но превышает потолок тела Kestrel в 30 МБ, поэтому вы получаете 413, а не 400.

Увеличьте лимит там, где он применяется

Пройдитесь по ним в зависимости от того, откуда приходит ваш 413. Большинству самостоятельно хостящихся приложений Kestrel нужен только первый.

1. Увеличьте или уберите глобальный лимит Kestrel

Настройте KestrelServerOptions.Limits.MaxRequestBodySize при запуске. Задайте конкретное число байт или null, чтобы убрать потолок полностью:

// .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();

Предпочитайте реальный потолок вместо null. Неограниченный размер тела - это вектор отказа в обслуживании: один клиент может передать в ваш процесс гигабайты и исчерпать память или диск. Выберите наибольшую легитимную загрузку, которую вы ожидаете, и добавьте запас, вместо того чтобы отключать защиту.

2. Задайте лимит для отдельного endpoint на контроллерах через атрибуты

Если большие загрузки принимает только один endpoint, не увеличивайте глобальный лимит всего приложения. На action контроллера или Razor Page используйте [RequestSizeLimit(bytes)], чтобы задать конкретный потолок, или [DisableRequestSizeLimit], чтобы убрать его только для этого action. Оба атрибута реализуют IRequestSizeLimitMetadata, который конвейер MVC читает при каждом запросе:

// .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 });
    }
}

Замените [RequestSizeLimit(...)] на [DisableRequestSizeLimit], когда action должен принимать сколь угодно большие тела, а у вас предусмотрена другая защита (потоковая передача, проверка квоты).

3. Задайте лимит для каждого запроса в minimal API

Minimal API не учитывают [RequestSizeLimit] так, как контроллеры, потому что в конвейере нет resource filter MVC, который бы его читал. Задайте лимит императивно через IHttpMaxRequestBodySizeFeature, прежде чем что-либо прочитает тело:

// .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 });
});

Сначала проверьте IsReadOnly. Как только сервер начал читать тело, лимит блокируется, и присваивание ему бросает InvalidOperationException. Именно поэтому этот приём не работает с параметром IFormFile в minimal API: model binding читает multipart-тело до того, как запустится ваш handler, так что к моменту, когда вы можете тронуть feature, она уже только для чтения (это корень issue dotnet/aspnetcore #50871, где большие multipart-загрузки в minimal API молча возвращали 200 с усечённым файлом). Для загрузок в minimal API привяжите HttpContext или HttpRequest, увеличьте лимит, а затем прочитайте форму сами через request.ReadFormAsync(), либо задайте лимит для всей группы небольшим middleware на MapGroup.

4. Увеличьте лимит multipart-формы для отправки форм

Если ваш 413 на самом деле 400 с InvalidDataException о длине multipart-тела, то сработавший лимит - это FormOptions.MultipartBodyLengthLimit, а не лимит Kestrel. Увеличьте его глобально через options или для отдельного action контроллера через [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) { /* ... */ }

Для большой загрузки обычно нужно увеличить оба: MaxRequestBodySize управляет всем запросом, а MultipartBodyLengthLimit управляет телом формы внутри него. Задайте им одинаковое значение.

5. За IIS увеличьте maxAllowedContentLength в web.config

Когда вы хостите за IIS (in-process или out-of-process), модуль фильтрации запросов IIS навязывает maxAllowedContentLength, по умолчанию 30 000 000 байт, до того как запрос достигнет Kestrel. Увеличение лимита Kestrel ничего не даёт, пока вы не увеличите и этот. Добавьте его в web.config:

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

У модуля in-process-хостинга есть второй, отдельный лимит, который можно задать в коде через 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
});

За IIS вам может понадобиться согласовать все три: maxAllowedContentLength в web.config, IISServerOptions.MaxRequestBodySize и MaxRequestBodySize в Kestrel. Запрос отклоняется по наименьшему из них.

6. Увеличьте потолок на обратном прокси

Если перед приложением стоит прокси, у него свой лимит, и он возвращает 413 ещё до того, как задействуется ваше приложение. В nginx директива называется client_max_body_size (по умолчанию 1 МиБ, что намного меньше значения Kestrel по умолчанию и часто становится сюрпризом):

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

Для YARP, API-шлюза или облачного балансировщика нагрузки найдите эквивалентную настройку размера запроса в конфигурации этого уровня. Увеличение лимитов на стороне приложения не поможет, пока прокси не пропустит тело.

Подводные камни и варианты

Ментальная модель: загрузка проходит через стек ворот размера (прокси, IIS, размер тела Kestrel, размер multipart-формы) и отклоняется первыми же воротами, которые она превышает. Найдите уровень, породивший ваш 413, увеличьте этот лимит до реального, ограниченного значения и сочетайте его с потоковой передачей, чтобы больший лимит не стал большей уязвимостью.

Связанное

Источники

Comments

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

< Назад