Настройка буферов Nginx: оптимизация `client_body_buffer_size` и `proxy_buffer_size`

Настройте буферы запросов и прокси-сервера Nginx без лишнего расхода памяти или ненужной буферизации на диск.

Настройка буферов Nginx: оптимизация client_body_buffer_size и proxy_buffer_size

Настройка буферов Nginx не сводится к тому, чтобы сделать каждый буфер огромным. Речь идет о том, какие данные должны храниться в памяти, какие можно безопасно сбросить на диск, а какие ответы следует передавать потоком, а не полностью буферизировать.

Запутывает то, что несколько директив звучат похоже. client_body_buffer_size применяется к телам запросов, поступающих от клиентов. proxy_buffer_size и proxy_buffers применяются к ответам, поступающим от вышестоящего сервера, когда Nginx выступает в роли обратного прокси. У FastCGI есть свои аналогичные директивы. Если настроить не ту сторону, ничего не улучшится.

Хорошая сессия настройки начинается с симптомов:

  • В журналах ошибок Nginx упоминаются временные файлы.
  • Загрузки или большие POST-запросы выполняются медленно.
  • Ответы API через обратный прокси зависают под нагрузкой.
  • Рабочие процессы используют больше памяти, чем ожидалось.
  • Большие заголовки ответов вызывают ошибки вышестоящего сервера.

У этих симптомов не одно и то же решение. Увеличение буферов может уменьшить дисковый ввод-вывод, но также увеличивает потребление памяти на каждый запрос. На загруженном сайте настройка, которая кажется крошечной для одного запроса, может стать большой при умножении на тысячи одновременных соединений.

Буферизация тела запроса: client_body_buffer_size

client_body_buffer_size управляет буфером, используемым при чтении Nginx тела клиентского запроса. Подумайте об отправке форм, JSON POST-телах и загрузках. Если тело запроса больше этого буфера, Nginx может записать его часть во временный файл в client_body_temp_path.

Базовая настройка выглядит так:

http {
    client_body_buffer_size 128k;
}

Это не меняет максимально допустимый размер загрузки. Он контролируется client_max_body_size:

server {
    client_max_body_size 20m;
    client_body_buffer_size 128k;
}

Эти две директивы отвечают на разные вопросы. client_max_body_size говорит: «Насколько большим может быть запрос, прежде чем Nginx отклонит его?» client_body_buffer_size говорит: «Какую часть тела запроса Nginx должен хранить в памяти перед использованием временного файла?»

Для JSON API, где большинство тел запросов меньше 32 КБ, а некоторые — 200 КБ, буфер 128k может уменьшить количество записей во временные файлы, не будучи чрезмерным. Для сервиса загрузки файлов, принимающего видео по 50 МБ, глобальная установка client_body_buffer_size 50m была бы плохим компромиссом. Вы резервировали бы слишком много памяти для рабочей нагрузки, где буферизация на диск может быть приемлемой.

Проверьте журнал ошибок на наличие подсказок, таких как:

[warn] a client request body is buffered to a temporary file

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

Буферизация прокси-ответов: proxy_buffer_size и proxy_buffers

Когда Nginx проксирует запросы к вышестоящему приложению, буферизация ответов обычно включена по умолчанию. Nginx быстро читает ответ вышестоящего сервера, сохраняет его в буферах и отправляет клиенту. Если ответ не помещается в буферы памяти, его часть может быть записана во временный файл. Официальная документация модуля прокси Nginx описывает это поведение и связывает запись во временные файлы с proxy_max_temp_file_size и proxy_temp_file_write_size.

Первый буфер управляется proxy_buffer_size:

proxy_buffer_size 16k;

Этот буфер обрабатывает заголовок ответа и первую часть ответа. Если ваш вышестоящий сервер отправляет большие заголовки, например, много куки, большие заголовки аутентификации или метаданные трассировки увеличенного размера, вы можете увидеть ошибки типа «upstream sent too big header». В этом случае часто нужно просматривать proxy_buffer_size.

Остальные буферы ответов управляются proxy_buffers:

proxy_buffers 8 32k;

Это означает восемь буферов по 32 КБ каждый для данных прокси-ответа. Это не означает, что каждый запрос всегда потребляет полный объем от начала до конца, но вы все равно должны рассматривать эти настройки как параметры памяти на запрос под нагрузкой.

Типичный блок обратного прокси выглядит так:

location /api/ {
    proxy_pass http://app_backend;

    proxy_buffering on;
    proxy_buffer_size 16k;
    proxy_buffers 8 32k;
    proxy_busy_buffers_size 64k;
}

Не копируйте это слепо. Небольшой HTML-сайт, JSON API и сервис загрузки файлов имеют разные потребности.

Временные файлы и proxy_max_temp_file_size

Если буферизация прокси-ответов включена и ответ больше настроенных буферов, Nginx может записать его часть на диск. proxy_max_temp_file_size ограничивает размер этого временного файла. В документации Nginx по умолчанию обычно указано 1024m, а установка значения 0 отключает буферизацию прокси-ответов во временные файлы.

location /api/ {
    proxy_pass http://app_backend;
    proxy_buffering on;
    proxy_max_temp_file_size 50m;
}

Используйте 0 с осторожностью:

proxy_max_temp_file_size 0;

Это не означает, что большие ответы становятся бесплатными. Это означает, что Nginx не будет использовать временные файлы для этих буферизированных прокси-ответов. В зависимости от ответа, скорости клиента и поведения буферизации вам может потребоваться больше памяти, другие настройки буферизации или потоковая передача.

Один важный нюанс из документации Nginx: ограничение proxy_max_temp_file_size не применяется к ответам, которые будут кэшироваться или сохраняться на диск. Если вы используете proxy_cache или proxy_store, просмотрите эти настройки отдельно.

Когда proxy_buffering off является лучшим ответом

Иногда не следует буферизировать ответ вообще. Потоковые конечные точки, события, отправляемые сервером, большие загрузки и длительные выходные данные часто работают лучше с отключенной буферизацией прокси:

location /events/ {
    proxy_pass http://app_backend;
    proxy_buffering off;
}

С отключенной буферизацией Nginx передает ответ вышестоящего сервера клиенту по мере его получения, вместо того чтобы пытаться собрать полный ответ. proxy_buffer_size по-прежнему важен, поскольку Nginx нужен буфер для данных, полученных от вышестоящего сервера, но proxy_buffers и поведение временных файлов становятся менее важными.

Не отключайте буферизацию глобально, если вы не понимаете компромисс. Буферизация защищает вышестоящие серверы от медленных клиентов. Если клиенты загружают данные медленно, а буферизация отключена, соединения с вышестоящим сервером могут оставаться занятыми дольше.

Буферы FastCGI для PHP и аналогичных настроек

Если Nginx взаимодействует с PHP-FPM, директивы proxy_* не подходят. FastCGI использует соответствующие имена:

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;

    fastcgi_buffer_size 16k;
    fastcgi_buffers 8 32k;
}

Применяется та же логика: первый буфер обрабатывает заголовки и начальные данные; массив буферов обрабатывает остальное содержимое ответа. Если PHP отправляет большие заголовки, возможно, потребуется внимание к fastcgi_buffer_size. Если большие страницы, сгенерированные PHP, сбрасываются на диск, проверьте fastcgi_buffers и рабочую нагрузку.

Как настраивать без угадывания

Начните с проверки журналов:

sudo tail -f /var/log/nginx/error.log

Ищите предупреждения о буферизации тел запросов или ответов вышестоящего сервера во временные файлы. Затем измерьте фактические размеры. Для API выберите образцы размеров запросов и ответов из журналов доступа, журналов приложений или вашей системы observability. Для загрузок разделите запросы метаданных и конечные точки загрузки файлов. Они не должны иметь одинаковые предположения.

Настраивайте в максимально узком контексте, который имеет смысл. Вместо этого:

http {
    client_body_buffer_size 10m;
}

предпочтите что-то вроде этого только для конечной точки загрузки:

location /upload/ {
    client_max_body_size 50m;
    client_body_buffer_size 512k;
    proxy_pass http://upload_backend;
}

Для JSON API с редкими ответами по 1 МБ вы можете настроить только этот API-локацию:

location /reports/ {
    proxy_pass http://report_backend;
    proxy_buffering on;
    proxy_buffer_size 32k;
    proxy_buffers 16 64k;
    proxy_max_temp_file_size 20m;
}

Затем рассчитайте наихудший случай. Если 1000 одновременных запросов могут использовать по несколько сотен КБ буферов ответов каждый, это реальная память. Оставьте место для рабочих процессов, TLS, соединений с вышестоящими серверами, кэшей, страничного кэша ОС и других сервисов.

Проверьте конфигурацию и следите за системой

После каждого изменения:

sudo nginx -t
sudo systemctl reload nginx

Затем следите за памятью, дисковым вводом-выводом и журналами ошибок. Успешная проверка синтаксиса доказывает только то, что файл парсится. Она не доказывает, что значения хороши.

Полезные проверки включают:

free -h
iostat -xz 1
sudo tail -f /var/log/nginx/error.log

Если предупреждения о временных файлах исчезают, но нагрузка на память растет, вы перестарались. Если память остается в норме, а дисковый ввод-вывод снижается во время затронутой рабочей нагрузки, изменение, вероятно, помогло.

Настройка буферов Nginx работает лучше всего, когда она локальна, измерена и привязана к реальному трафику. Держите обычные запросы в памяти, когда это экономит задержку, позволяйте действительно большим загрузкам или скачиваниям использовать правильный путь и избегайте глобальных настроек, которые заставляют каждое соединение платить за самый большой крайний случай.