Новинка Виртуальный VDS сервер в Финляндии от 390р
Выберите продукт

Большие загрузки и выдача файлов: client_max_body_size, upload_max_filesize, X‑Accel‑Redirect

413 Request Entity Too Large, пустой $_FILES, обрывы по таймаутам и «тяжёлая» раздача через PHP. Разбираем Nginx+PHP: лимиты и таймауты, временные каталоги, X‑Accel‑Redirect и корректный resume через Range, плюс чек‑листы и конфиги.
Большие загрузки и выдача файлов: client_max_body_size, upload_max_filesize, X‑Accel‑Redirect

Большие загрузки и тяжёлая выдача файлов — одни из самых «кинжальных» сценариев для веб-сервера. Здесь всплывают 413 Request Entity Too Large, 499 и 504 таймауты, пустые массивы $_FILES, забитые временные каталоги и «горячие» PHP-процессы, которые зачем-то стримят клипу 8 ГБ пользователю. В этой статье разбираем, как правильно согласовать лимиты Nginx и PHP (client_max_body_size, upload_max_filesize, post_max_size), какие таймауты действительно важны, как организовать безопасную отдачу больших файлов через X-Accel-Redirect и сделать так, чтобы пользователи могли докачивать файлы (resume downloads) через Range-запросы.

Как проходит загрузка: краткая схема Nginx + PHP-FPM

Клиент отправляет POST multipart/form-data на ваш сайт. Nginx принимает тело запроса и складывает его во временные файлы (по частям), затем передаёт запрос в PHP-FPM (через FastCGI). PHP читает данные из файлов/потока и раскладывает их в $_FILES. Если по пути что-то «узкое» — запрос отклоняется или рвётся по таймауту.

Критично: при несогласованных лимитах тело запроса может быть отброшено ещё на уровне Nginx (413) — до попадания в PHP. Тогда $_FILES будет пустым.

Три главных лимита размера

Nginx: client_max_body_size

client_max_body_size ограничивает общий размер тела запроса. Применяется на ранней стадии. Если превышен — Nginx вернёт 413 без обращения к приложению. Удобно, если вы хотите экономить ресурсы и трафик, но пользователь увидит стандартную страницу ошибки, если не настроить error_page.

PHP: upload_max_filesize и post_max_size

upload_max_filesize — максимальный размер одного загружаемого файла. post_max_size — максимальный общий размер тела POST, включая все поля и файлы. Обычно post_max_size должен быть больше или равен upload_max_filesize с некоторым запасом (пары мегабайт хватает).

Типичные симптомы несогласованности:

  • Nginx меньше PHP: пользователь получает 413 мгновенно, приложение не видит запрос.
  • PHP меньше Nginx: загрузка идёт долго, а потом приложение сообщает о превышении лимита; ресурсы тратятся больше, зато можно показать дружелюбную ошибку.

Практика: в проектах, где UX важнее экономии трафика, ставьте лимит Nginx чуть больше лимитов PHP, чтобы ошибка обрабатывалась приложением. В API и сильной нагрузке — наоборот, Nginx жёстче режет заранее.

FastFox VDS
Облачный VDS-сервер
Виртуальные серверы с быстрым запуском и гибкой конфигурацией от 390₽ / мес
Доступные локации
Россия Нидерланды Финляндия

Не только размер: таймауты и буферы

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

  • client_body_timeout (Nginx): время ожидания следующего фрагмента тела запроса. Если пользователь грузит медленно — увеличьте.
  • send_timeout (Nginx): таймаут отправки ответа клиенту (включая выдачу файлов).
  • keepalive_timeout (Nginx): полезно, но к загрузке напрямую не относится.
  • fastcgi_read_timeout (Nginx): сколько ждать ответа от PHP. Большой парсинг/обработка — увеличьте осторожно.
  • client_body_buffer_size и tmp-path-и Nginx: куда и как буферизуется тело запроса до передачи в бэкенд.
  • max_execution_time, max_input_time (PHP): ограничивают длительность обработки/чтения входа.
  • memory_limit (PHP): не относится напрямую к файлу, но может «сбросить» скрипт при неосторожном чтении в память.
  • upload_tmp_dir (PHP): временный каталог для загружаемых файлов; проверьте квоты/прав доступа/свободное место.

Если нужно тонко управлять параметрами и I/O под нагрузкой, удобнее делать это на VDS. Если вы обслуживаете проект на шаред-площадке — уточните доступные лимиты у провайдера или используйте наш виртуальный хостинг с актуальными сборками PHP и Nginx.

Поток загрузки в Nginx+PHP и ключевые таймауты/буферы

Где хранится тело запроса: временные каталоги

При больших загрузках тело запроса буферизуется во временные файлы. Две точки отказа — нехватка места и права:

  • Nginx: client_body_temp_path (по умолчанию каталог temp внутри префикса Nginx). Должен лежать на файловой системе с достаточным свободным местом и скоростью.
  • PHP: upload_tmp_dir. Если не задан, используется системный TMP. На хостингах часто ограничен квотой.

Если места мало — загрузки «случайно» обрываются, в логах Nginx/FPM будут ошибки наподобие «No space left on device» или проблемы с перемещением файла (move_uploaded_file возвращает false).

Базовый чек-лист настроек

  1. Определите максимально допустимый размер загружаемых файлов и общий размер формы.
  2. Выберите стратегию отказа (ранний 413 в Nginx или обработка на уровне приложения).
  3. Согласуйте client_max_body_size, post_max_size, upload_max_filesize.
  4. Проверьте client_body_timeout и fastcgi_read_timeout.
  5. Назначьте и проверьте каталоги временных файлов в Nginx и PHP, следите за диском и inode.
  6. Убедитесь, что ulimit и системные лимиты файлов/сокетов достаточны под пиковую нагрузку.

Пример конфигурации Nginx для больших загрузок

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    client_max_body_size 1024m;  # 1 ГБ
    client_body_timeout 120s;
    send_timeout 120s;

    # При необходимости перенесите temp в отдельный быстрый раздел
    # client_body_temp_path /var/lib/nginx/body 1 2;
}

server {
    listen 80;
    server_name example.local;

    # Кастомная страница для 413
    error_page 413 = /413.html;
    location = /413.html { internal; }

    location / {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root/index.php;
        fastcgi_pass unix:/run/php/php-fpm.sock;
        fastcgi_read_timeout 180s;
    }
}

PHP (php.ini) под крупные загрузки

file_uploads = On
upload_max_filesize = 1024M
post_max_size = 1026M
max_file_uploads = 50
max_input_time = 180
max_execution_time = 180
memory_limit = 512M
; upload_tmp_dir = /path/to/php-tmp  ; при необходимости вынесите на отдельный том

Частая ошибка — поставить upload_max_filesize больше post_max_size. В таком случае загрузка «сломается» тихо: файл будто бы игнорируется, а $_FILES пустой. Обязательно проверяйте корректность значений и единиц (M/G vs Mb/Gb).

Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Проверка через curl

Полезно проверять лимиты без фронтенда:

# Отправить файл и проверить код ответа
curl -i -X POST \
  -F "file=@./big.bin" \
  http://example.local/upload

# Проверить, поддерживает ли сервер докачку (Range)
curl -I http://example.local/download/big.iso
curl -r 0-1048575 -o part.bin http://example.local/download/big.iso

Почему большие файлы нельзя отдавать через PHP

Технически можно вызвать readfile() и написать цикл с fpassthru(). Но это перегружает PHP-FPM: процесс будет занят десятками секунд или минут, упираясь в send_timeout и сетевую скорость клиента. К тому же PHP хуже поддерживает Range-запросы и докачку «из коробки»; нужно вручную ставить заголовки, следить за буферами, отключать output_buffering, избегать случайного вывода и пр.

Оптимальный путь — переложить выдачу файла на Nginx через внутренний редирект X-Accel-Redirect. Так Nginx сам будет читать файл с диска, эффективно буферизовать и корректно работать с Range-запросами и медленными клиентами, не блокируя PHP.

Схема X‑Accel‑Redirect

  1. Файлы хранятся вне веб-корня или в защищённом каталоге, недоступном напрямую.
  2. PHP выполняет авторизацию/аудит, проверяет право доступа к файлу.
  3. PHP возвращает пользователю обычный ответ, но добавляет заголовок X-Accel-Redirect с внутренним путём, понятным Nginx.
  4. Nginx перехватывает заголовок и отдаёт файл напрямую, применяя свои оптимизации.

Nginx: защищённое внутреннее хранилище

server {
    listen 80;
    server_name example.local;

    # Внутренний location для X-Accel-Redirect
    location /protected/ {
        internal;
        alias /data/protected/;  # файлы лежат вне веб-корня
        # Опционально ограничить скорость
        # limit_rate_after 5m;
        # limit_rate 1m;
    }

    location /download {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root/download.php;
        fastcgi_pass unix:/run/php/php-fpm.sock;
    }
}

PHP: заголовки и редирект для Nginx

<?php
// download.php

// 1) Авторизация и проверка доступа к файлу
$userId = 123; // пример
$filePath = "/data/protected/reports/report-{$userId}.pdf";
if (!is_file($filePath)) {
    http_response_code(404);
    exit('Not found');
}

$publicName = 'report.pdf';
$internalUri = '/protected/' . basename($filePath);
$filesize = filesize($filePath);

// 2) Заголовки ответа (важно: Content-Type/Disposition/Length)
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . $publicName . '"');
header('Content-Length: ' . $filesize);
header('Accept-Ranges: bytes'); // Nginx всё равно поддержит, но явно — понятнее

// 3) Главный трюк: поручаем отдачу Nginx
header('X-Accel-Redirect: ' . $internalUri);
exit;

Обратите внимание: location /protected/ помечен как internal, значит прямой доступ к нему запрещён. Только приложение может «включать» выдачу через заголовок X-Accel-Redirect.

Отдача защищённых файлов через X‑Accel‑Redirect в Nginx

Поддержка Range и resume downloads

При отдаче файлов Nginx умеет обрабатывать заголовок Range и корректно отвечать частями (206 Partial Content) со всеми нужными заголовками (Accept-Ranges, Content-Range). Благодаря этому докачка в большинстве браузеров и менеджеров загрузок работает «из коробки». Если файл отдаётся через X-Accel-Redirect, поддержку Range обеспечивает сам Nginx, а не PHP.

Для больших файлов полезно включить низкоуровневые оптимизации чтения:

  • sendfile on; — отдача напрямую из файлового кэша ядра.
  • tcp_nopush on; и tcp_nodelay on; — аккуратная отправка больших блоков.
  • aio on; — асинхронное чтение (актуально для некоторых ОС и паттернов I/O).

Если нужно сбалансировать сжатие, кеш и производительность PHP на шаред-площадке — посмотрите также разбор оптимизаций в материале про PHP, OPCache и Brotli.

Тонкости буферизации и заголовков

  • Content-Length: лучше указывать точный размер, чтобы клиенты корректно показывали прогресс и умели докачивать. При X-Accel-Redirect Nginx может сам вычислить длину файла, но явный Content-Length в ответе от PHP не навредит.
  • Content-Type: ставьте корректный MIME; не полагайтесь на дефолтный application/octet-stream, если тип известен.
  • Content-Disposition: attachment; filename="..." — для «скачивания как»; аккуратнее с не-ASCII именами (используйте filename* при необходимости).
  • Кеширование: если файлы статичны, используйте expires/cache-control в internal-локации. Для приватного контента ставьте Cache-Control: private.

Типовые ошибки и их диагностика

413 Request Entity Too Large

Причины: client_max_body_size меньше фактического размера. Лечится увеличением лимита и/или настройкой кастомной страницы ошибки. Если хотите, чтобы обработка была в приложении, увеличьте лимит Nginx выше лимитов PHP.

Пустой $_FILES

Чаще всего post_max_size меньше, чем отправленный размер, или закончился диск в upload_tmp_dir. Проверьте php.ini, права на TMP и логи PHP-FPM. Не забывайте про max_file_uploads при множественных инпутах.

499/504, «загрузка останавливается»

499 — клиент сам закрыл соединение (часто из-за низкой скорости или потери сети). 504 Gateway Timeout — истёк fastcgi_read_timeout при обработке сервером. Увеличьте таймауты, проверьте диск, I/O и сетевые условия. При загрузке с очень медленных линий поднимите client_body_timeout.

Выдача больших файлов «через PHP» под завязку грузит CPU

Переведите выдачу на X-Accel-Redirect. Контролируйте скорость и используйте sendfile. Это резко разгружает PHP-FPM и позволяет обслуживать больше одновременных скачиваний.

Недокачка и «битые» файлы

Проверьте, не перезаписывает ли приложение заголовки, нет ли лишнего вывода до заголовков (BOM, пробелы), и что по пути нет обратного прокси, который режет Range. Для больших файлов лучше исключить gzip на лету.

Логи для анализа больших запросов

Для наблюдения за проблемами полезно логировать длину запроса и потраченное время:

log_format upload '$remote_addr - $remote_user [$time_local] '
                  '"$request" $status $body_bytes_sent '
                  '$request_length $request_time';
access_log /var/log/nginx/upload.log upload;

$request_length покажет, насколько большой был запрос, а $request_time поможет понять, где узкое место (сеть, диск, приложение).

Рекомендации по проектированию

  • Сохраняйте загружаемые файлы вне веб-корня и публикуйте их только через X-Accel-Redirect с авторизацией.
  • Ограничивайте максимально допустимые размеры по ролям/эндпоинтам (админка vs публичная форма), не размывайте единственный глобальный лимит.
  • Следите за временными каталогами и дисками (метрики, алерты). «Внезапная» нехватка места — частая причина падений загрузок.
  • Если фронтенд поддерживает чанковую загрузку, используйте серверную сборку частей — она стабильнее на нестабильных сетях. На сервере всё равно проверьте итоговый размер.
  • Для файлов >2–4 ГБ (особенно на сетевых FS) уделите внимание aio и RAID/блочному уровню — от этого заметно зависит стабильность выдачи.

Если планируете самостоятельную администрируемую инфраструктуру, посмотрите обзор панелей для управления своим сервером: сравнение панелей для VDS.

Итоги

Чтобы большие загрузки работали стабильно, согласуйте лимиты client_max_body_size, upload_max_filesize и post_max_size, не забудьте про таймауты и временные каталоги, а для выдачи переходите на X-Accel-Redirect. Так вы обеспечите предсказуемость, снимете нагрузку с PHP-FPM, получите корректную поддержку Range/ресюма и убережёте пользователей от «битых» скачиваний. Не бойтесь жёстко резать слишком большие запросы на уровне Nginx там, где это оправдано, и давайте приложению контролировать UX там, где это важнее.

Поделиться статьей

Вам будет интересно

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

После одновременной остановки всех узлов Galera обычного запуска MariaDB недостаточно. Сначала найдите сервер с последней транзакц ...
CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

Разберём, почему CoreDNS перезапускается или отвечает SERVFAIL, как обнаружить замкнутую пересылку DNS-запросов, проверить доступн ...
Apache Kafka KRaft на VDS: отдельные controller и broker

Apache Kafka KRaft на VDS: отдельные controller и broker

Соберём новый Kafka-кластер без ZooKeeper на шести Linux VDS: три узла controller образуют отказоустойчивый metadata quorum, а три ...