Иногда сайт на 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 под ваши типовые ответы.

Что означают размеры на практике
Рассчитываем верхнюю границу памяти на один ответ:
- Максимум, который 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.
Диагностика: как понять, что упёрлись в буферы
Полезные признаки и приёмы:
- Логи времени: добавьте в формат
$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: практическая оптимизация».

Настройки в PHP, влияющие на поведение
output_bufferingвphp.ini(часто 4096) — небольшая буферизация на стороне PHP. Она сглаживает «дробную» запись и полезна при включённой буферизации Nginx.zlib.output_compression— не смешивайте компрессию в PHP и Nginx одновременно, оставьте компрессию на одной стороне (обычно в Nginx).- Фреймворки, делающие chunked‑отдачу, могут конфликтовать с идеей крупной буферизации. Если вам нужна потоковая передача (SSE, экспорт), рассмотрите
fastcgi_buffering offточечно под соответствующие маршруты.
Чек‑лист настройки под ваши ответы
- Соберите метрики: p50/p95 размера тела и заголовков, upstream_response_time vs request_time.
- Определите целевой класс ответов: маленькие API, средние страницы, большие экспортные.
- Подберите буферы так, чтобы p90 помещался в оперативные буферы. Для p99 — решайте: память или допустимая запись на диск.
- Выставьте разумный
fastcgi_busy_buffers_size(2–4 буфера), чтобы не душить чтение изphp-fpm. - Ограничьте или запретите временные файлы там, где важна минимальная latency и ответы компактны.
- Перезапустите конфиг, прогоните нагрузочный тест, проверьте логи и 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 (например, на виртуальном хостинге), ориентируйтесь на рекомендации поддержки или используйте преднастроенные профили.


