Выберите продукт

fastcgi_buffers и busy_buffers_size: устраняем «затыки» на отдаче PHP‑ответов

Если PHP‑страницы иногда «замирают», а TTFB скачет, часто виноваты настройки буферов FastCGI. Поясню, как работают fastcgi_buffers и busy_buffers_size, чем грозит запись во временные файлы и дам проверенные конфиги для типовых нагрузок.
fastcgi_buffers и busy_buffers_size: устраняем «затыки» на отдаче PHP‑ответов

Иногда сайт на PHP работает «быстро, но не всегда»: под нагрузкой или у части клиентов всплывают случайные подлагивания — увеличивается TTFB, waterfall в инструментах разработчика показывает ступеньки, а в логах видно рост общего времени запроса при нормальном времени ответа от PHP. В большинстве случаев это проблема буферизации FastCGI в Nginx: неверно подобранные fastcgi_buffers, fastcgi_buffer_size и fastcgi_busy_buffers_size приводят к записи ответа на диск, стопорам чтения от upstream и скачкам latency.

Как работает буферизация FastCGI в Nginx

Схема простая: php-fpm пишет ответ в сокет, Nginx читает его и отдаёт клиенту. Чтобы не зависеть от скорости клиента и сгладить пики, Nginx буферизует ответ в памяти, а при нехватке памяти — во временные файлы. Управляют этим:

  • fastcgi_buffering — включает буферизацию ответа от upstream (по умолчанию включено).
  • fastcgi_buffer_size — размер первого буфера (обычно для заголовков и начала тела).
  • fastcgi_buffers — количество и размер дополнительных буферов для тела ответа.
  • fastcgi_busy_buffers_size — сколько из этих буферов может одновременно находиться «в выдаче» клиенту.
  • fastcgi_temp_file_write_size и fastcgi_max_temp_file_size — контроль записи ответа во временные файлы.

Значения по умолчанию варьируются от размера страницы памяти ОС (4К/8К):

  • fastcgi_buffer_size — примерно 4k или 8k.
  • fastcgi_buffers — 8 буферов по 4k или 8k.
  • fastcgi_busy_buffers_size — около 8k или 16k.

Идея проста: держать «горячую» часть ответа PHP в оперативной памяти и отдавать клиенту без остановки чтения от upstream. Если буферов мало или они малы — Nginx вынужден тормозить чтение либо писать временный файл на диск.

Где появляется «затык»: роль busy_buffers_size

Пока клиент медленный или канал нестабилен, часть буферов остаётся занята на выдаче. Порог задаёт fastcgi_busy_buffers_size. Как только суммарный размер занятых буферов достигает этого порога, Nginx прекращает читать из php-fpm до тех пор, пока клиент не освободит место. Это и есть «обратное давление» (backpressure): PHP уже готов отдавать, но чтение останавливается. В результате:

  • растёт время жизни соединения с php-fpm, уменьшая его общий RPS;
  • увеличивается request_time при нормальном upstream_response_time;
  • при недостатке памяти начинается запись во временные файлы, что бьёт по диску и latency.

Поэтому критично правильно подобрать сочетание fastcgi_buffer_size, fastcgi_buffers и fastcgi_busy_buffers_size под ваши типовые ответы.

Скачки TTFB и ступеньки waterfall при нехватке буферов в Nginx

Что означают размеры на практике

Рассчитываем верхнюю границу памяти на один ответ:

  • Максимум, который Nginx держит в оперативной памяти на запрос, оценивается как fastcgi_buffer_size + sum(fastcgi_buffers).
  • fastcgi_busy_buffers_size должен быть меньше суммарного размера буферов; обычно выбирают 2–4 буфера. Если поставить больше общего, Nginx откажет при проверке конфига.
  • Буферы действительно выделяются при необходимости и возвращаются после завершения запроса, но при высокой конкуренции планируйте пиковое потребление памяти на количество одновременных ответов.

Также, если заголовки ответа PHP велики (например, много Set-Cookie), маленький fastcgi_buffer_size вызовет ошибку вроде «upstream sent too big header». Увеличивайте fastcgi_buffer_size до 16k–64k.

Когда и как Nginx пишет на диск

Если ответ больше, чем помещается в оперативных буферах (или клиент слишком медленный), Nginx может начать буферизацию во временный файл. Это увеличивает задержки и даёт лишнюю нагрузку на диск. Контролируется директивами:

  • fastcgi_temp_file_write_size — размер порции записи во временный файл.
  • fastcgi_max_temp_file_size — максимальный размер временного файла; при 0 запись во временные файлы запрещена.

Запрет временных файлов (fastcgi_max_temp_file_size 0) полезен для API с небольшими ответами и высокой latency у клиентов: тогда вместо диска будет остановлено чтение от upstream (backpressure), что лучше, чем I/O. Но если ответы крупные, такой запрет может растянуть соединения с php-fpm и снизить пропускную способность. Оцените средний и 95‑й перцентили размера тела ответа.

Практические пресеты для PHP‑проектов

1) API и типичный сайт: ответы до 256–512 KB

Цель: исключить запись на диск и минимизировать «стопоры» при отдаче. Пример значений:

fastcgi_buffering on;
fastcgi_buffer_size 32k;
fastcgi_buffers 8 32k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 64k;
fastcgi_max_temp_file_size 0;

Комментарий: суммарно до ~288 KB в памяти. Этого хватает для большинства HTML+JSON ответов. busy_buffers_size ≈ 4 буфера обеспечивает устойчивую отдачу при умеренно «медленных» клиентах.

2) Тяжёлые страницы: 0.5–2 MB (лендинги, каталоги)

Цель: снизить вероятность записи на диск и удержать большую часть ответа в памяти.

fastcgi_buffering on;
fastcgi_buffer_size 32k;
fastcgi_buffers 16 64k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 128k;
fastcgi_max_temp_file_size 64m;

Комментарий: в памяти до ~1.06 MB. Если у части клиентов канал совсем «узкий», временный файл включаем, но ограничиваем. Для критичных API лучше уменьшите размер страницы, чем наращивать буферы до гигабайт.

3) Потоковая отдача/экспорт (большие файлы)

Цель: не душить память и не писать многогигабайтные временные файлы.

fastcgi_buffering off;
# или заголовок от приложения: X-Accel-Buffering: no

Комментарий: когда ответ большой и его всё равно нельзя уместить в память, проще стримить клиенту. При этом повышается зависимость upstream от скорости клиента — учитывайте это в пуле php-fpm.

Полный пример локации для PHP

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_pass unix:/run/php/php-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

    fastcgi_read_timeout 60s;
    fastcgi_buffering on;
    fastcgi_buffer_size 32k;
    fastcgi_buffers 8 32k;
    fastcgi_busy_buffers_size 128k;
    fastcgi_temp_file_write_size 64k;
    fastcgi_max_temp_file_size 0;
}

Подбирайте числа под реальные размеры ответов. Для проектов с огромными заголовками увеличивайте fastcgi_buffer_size до 64k.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Диагностика: как понять, что упёрлись в буферы

Полезные признаки и приёмы:

  • Логи времени: добавьте в формат $request_time и $upstream_response_time. Если второе стабильно низкое, а первое скачет — клиентская отдача тормозит, и буферизация может быть узким местом.
  • Размеры ответов: логируйте $body_bytes_sent, посчитайте p50/p95. Сравните с суммой ваших буферов.
  • Ошибки заголовков: «upstream sent too big header...» — признак маленького fastcgi_buffer_size.
  • Диск: всплески I/O во время пиков — вероятная запись временных файлов. Подтвердите через системный мониторинг.
log_format timed '$remote_addr - $request $status $body_bytes_sent '
                 '$request_time $upstream_response_time';
access_log /var/log/nginx/access.log timed;

Для безопасных экспериментов с буферами поднимите стенд на VDS и прогоните нагрузочный тест до и после изменений.

Баланс: память на воркер vs конкуренция

Память тратится на уровне воркера Nginx. При оценке смотрите на «самый толстый» сценарий: одновременно N запросов с p95‑размером ответа. Верхняя прикидка — N * (fastcgi_buffer_size + sum(fastcgi_buffers)) на воркер. Обычно достаточно 2–4 воркеров, но если их больше — учитывайте мультипликатор.

Взаимодействие с gzip/brotli

Компрессия в Nginx уменьшает сетевой трафик, но для компрессии нужны дополнительные буферы, и иногда Nginx дождётся накопления части ответа. Если активно используете gzip/brotli для HTML/JSON, предпочитайте немного большие fastcgi_buffers и не зажимайте busy_buffers_size. Это сгладит фрагментацию и уменьшит задержки через редкие flush. Подробно про компрессию и кэширование читайте в материале «OPCache и Brotli: практическая оптимизация».

Память на запрос: расчёт fastcgi_buffer_size, fastcgi_buffers и busy_buffers_size

Настройки в PHP, влияющие на поведение

  • output_buffering в php.ini (часто 4096) — небольшая буферизация на стороне PHP. Она сглаживает «дробную» запись и полезна при включённой буферизации Nginx.
  • zlib.output_compression — не смешивайте компрессию в PHP и Nginx одновременно, оставьте компрессию на одной стороне (обычно в Nginx).
  • Фреймворки, делающие chunked‑отдачу, могут конфликтовать с идеей крупной буферизации. Если вам нужна потоковая передача (SSE, экспорт), рассмотрите fastcgi_buffering off точечно под соответствующие маршруты.

Чек‑лист настройки под ваши ответы

  1. Соберите метрики: p50/p95 размера тела и заголовков, upstream_response_time vs request_time.
  2. Определите целевой класс ответов: маленькие API, средние страницы, большие экспортные.
  3. Подберите буферы так, чтобы p90 помещался в оперативные буферы. Для p99 — решайте: память или допустимая запись на диск.
  4. Выставьте разумный fastcgi_busy_buffers_size (2–4 буфера), чтобы не душить чтение из php-fpm.
  5. Ограничьте или запретите временные файлы там, где важна минимальная latency и ответы компактны.
  6. Перезапустите конфиг, прогоните нагрузочный тест, проверьте логи и I/O.

Частые вопросы и грабли

  • Можно ли просто выкрутить всё «на максимум»? Теоретически да, но практически упрётесь в память и конкуренцию. Держите баланс: буферы ровно под ваши размеры.
  • Почему при росте busy_buffers_size стало лучше? Вы позволили Nginx держать больше данных «на выдаче», реже останавливая чтение. Но предел — суммарный объём буферов.
  • Ошибка «too big header» — увеличивайте fastcgi_buffer_size (16k–64k), иногда и fastcgi_buffers.
  • Мобильные клиенты и высокое RTT — чаще растёт request_time без роста upstream_response_time. Увеличьте буферы или запретите временные файлы для соответствующих локаций.
  • HTTP/2/3 — мультиплексирование не отменяет буферизацию ответа. Принципы подбора буферов те же.

Итоги

«Затыки» при отдаче PHP‑ответов в nginx чаще всего связаны с неподходящими настройками fastcgi_buffers и fastcgi_busy_buffers_size. Пройдитесь по данным о размерах ответов, выставьте буферы так, чтобы типичные ответы укладывались в память, а «занятые» буферы могли удерживать непрерывную выдачу без остановки чтения из php-fpm. Там, где потоки действительно большие, не стесняйтесь точечно отключать буферизацию или разрешать временные файлы с адекватными лимитами. Если вы не управляете Nginx (например, на виртуальном хостинге), ориентируйтесь на рекомендации поддержки или используйте преднастроенные профили.

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

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

Debian/Ubuntu: конфликт systemd-resolved DNSStubListener на 127.0.0.53 с dnsmasq, Unbound и BIND OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: конфликт systemd-resolved DNSStubListener на 127.0.0.53 с dnsmasq, Unbound и BIND

Если локальный DNS в Debian или Ubuntu не стартует с ошибкой address already in use, причина часто в systemd-resolved и DNSStubLis ...
Debian/Ubuntu: как исправить NFS mount.nfs: access denied by server while mounting OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить NFS mount.nfs: access denied by server while mounting

Ошибка mount.nfs: access denied by server while mounting в Debian и Ubuntu обычно указывает на проблему на стороне NFS-сервера: не ...
Debian/Ubuntu: как устранить конфликт systemd-resolved DNSStubListener с BIND9, dnsmasq и AdGuard Home OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как устранить конфликт systemd-resolved DNSStubListener с BIND9, dnsmasq и AdGuard Home

Если в Debian или Ubuntu DNS-сервер не стартует из-за ошибки port 53 busy, часто причина в systemd-resolved с локальным слушателем ...