Если сайт на VDS периодически ловит 502/504, «зависает» на пиках трафика или прожорливо съедает память, почти всегда виноват не сам PHP, а настройки менеджера процессов PHP‑FPM и несогласованные таймауты в стеке. В этой статье собрал рабочие практики: как выбрать режим pm
, как посчитать реальный pm.max_children
под вашу нагрузку, как уменьшить memory footprint, что сделать с Opcache
и как выстроить стабильные таймауты без сюрпризов. Если вы только выбираете инфраструктуру или планируете миграцию, под такие задачи лучше подходит VDS — вы управляете ресурсами и FPM‑пулами.
Коротко о том, как PHP‑FPM обслуживает запросы
PHP‑FPM — это пул процессов: один мастер и несколько воркеров (дочерних процессов). Каждый воркер обрабатывает один запрос одновременно. Когда воркеров не хватает, запросы становятся в очередь на уровне сокета или отваливаются по таймауту на стороне фронтенда (обычно Nginx). Ваша цель — поддерживать такой размер пула, чтобы при пиках не уходить в очередь, при простое — не держать лишние процессы, а память укладывалась в лимиты сервера.
За количество и жизненный цикл воркеров отвечает process manager
, настраиваемый через pm
:
pm = dynamic
— держит минимум/максимум воркеров и подстраивает их число под текущую нагрузку.pm = ondemand
— спит и порождает воркеров по запросу; простаивающие процессы засыпаются.pm = static
— фиксированное число воркеров; минимальная латентность, максимальная предсказуемость по цене памяти.
Для типичного веб‑трафика с постоянными запросами лучше
pm = dynamic
. Для редких всплесков/кронов —ondemand
.static
оправдан на высоконагруженных и чувствительных к задержкам системах при точном контроле памяти.
Память: из чего складывается footprint пула
Память потребляют: мастер‑процесс PHP‑FPM, воркеры, общие сегменты (в частности Opcache
), расширения PHP, а также ваши приложения (включая кеши в памяти и большие структуры в процессе запроса). Важно понимать, что метрики RSS/VSS не показывают «чистую» нагрузку, потому что часть памяти разделяется между процессами. Для оценки реального расхода на один воркер ориентируйтесь на PSS (proportional set size) или считайте усреднённое private dirty.
Как измерить «стоимость» одного воркера
Простой путь — дать системе поработать под реальной нагрузкой 3–5 минут, затем снять срез по процессам пула и усреднить:
# Посмотреть воркеров пула www по Unix‑сокету
ps -o pid,rss,cmd -C php-fpm | grep "php-fpm: pool www"
# Более детально по одному pid (замените 1234):
pmap -x 1234 | tail -n 1
# Если доступен smem (даёт PSS):
smem -r -P "php-fpm: pool www" -c "pid pss rss command" | sort -k2 -n
Снимите минимум 10–20 процессов и возьмите медиану PSS: это наиболее честная оценка памяти «на одного ребёнка» с учётом разделяемых сегментов. Отдельно запишите объём Opcache
и прочих демонов (Nginx, базы данных, Redis и т. п.).
Расчёт pm.max_children: методика и пример
Формула простая: из доступной памяти вычесть резервы под ОС и фоновые сервисы, вычесть общий сегмент Opcache
и прочие «глобальные» потребители, остаток разделить на медианный PSS одного воркера и округлить вниз.
Шаги:
- Определите «бюджет» памяти: общая RAM минус резерв ОС (обычно 10–20% для дискового кеша и ядра).
- Вычтите память под Nginx, базу данных, Redis/мессенджеры и прочие компоненты.
- Вычтите объём
Opcache
(opcache.memory_consumption
+ накладные). - Оставшийся объём разделите на PSS воркера.
Пример для VDS 4 ГБ, LEMP, небольшой база и Redis:
- Резерв ОС: ~512 МБ.
- Nginx + системное: ~150 МБ.
- MariaDB: ~700 МБ под реальную рабочую нагрузку (после тюнинга буферов).
- Redis: ~100 МБ.
- Opcache: 192 МБ.
- Остаток: 4096 − 512 − 150 − 700 − 100 − 192 ≈ 2442 МБ.
- Медиана PSS воркера: допустим 60 МБ.
pm.max_children = floor(2442 / 60) = 40
.
Далее уменьшите на 10–20% как «подушку», если есть риск пиковых скриптов (бэкенд‑импорт, отчёты). Это даст pm.max_children
≈ 32–36. Если используете pm = static
, запас лучше увеличить.

Режимы process manager: когда dynamic
, когда ondemand
pm = dynamic
Ключевые параметры: pm.max_children
, pm.start_servers
, pm.min_spare_servers
, pm.max_spare_servers
, pm.max_requests
.
Рекомендации:
pm.start_servers
поставьте близко к среднему количеству одновременных запросов при рабочем трафике (например, 30–50% отpm.max_children
).pm.min_spare_servers
— 20–30% отpm.max_children
, чтобы избежать «холодных стартов» при всплеске.pm.max_spare_servers
— 50–70% отpm.max_children
, чтобы не держать лишнее при просадке нагрузки.pm.max_requests
— 500–2000. Это «перезапуск по старости», защищает от утечек в расширениях и приложениях.
pm = ondemand
Ключевые параметры: pm.max_children
, pm.process_idle_timeout
, pm.max_requests
.
Рекомендации:
pm.process_idle_timeout
10–60 секунд: агрессивно выгружать воркеры в простое и экономить память.- При ожидаемых всплесках подготовьте warmup (например, вызвать пару лёгких страниц Cron‑ом) — иначе холодный старт добавит латентности.
pm.max_children
считайте так же, как дляdynamic
, но учитывайте, что реальное количество воркеров будет «пилить» вверх/вниз.
pm = static
Удобен, когда SLA на латентность критичен и нагрузка хорошо прогнозируема. Устанавливаете pm.max_children
как фиксированное число процессов, остальное — через внешнее масштабирование (например, несколько пулов, разные версии PHP или балансировка по бэкендам). Память должна позволять держать весь пул постоянно. Если работаете с панелью администрирования, пригодится обзор вариантов: сравнение панелей для VDS.
Базовый шаблон пула и значения по умолчанию
Ниже — пример пула, ориентированного на стабильный веб‑трафик на pm = dynamic
. Проверьте реальные пути/пользователя под вашу систему.
[www]
user = www-data
group = www-data
listen = /run/php/php-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
listen.backlog = 1024
pm = dynamic
pm.max_children = 36
pm.start_servers = 12
pm.min_spare_servers = 8
pm.max_spare_servers = 24
pm.max_requests = 1000
; Для ondemand вместо блоков start/min/max используйте:
; pm = ondemand
; pm.process_idle_timeout = 20s
; Лимиты и диагностика
rlimit_files = 32768
request_terminate_timeout = 65s
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/www-slow.log
catch_workers_output = yes
; Статус и ping (публикуйте только локально)
pm.status_path = /status
ping.path = /ping
ping.response = pong
Пара замечаний:
listen.backlog
не спасёт от недостатка воркеров, но уменьшит вероятность отказов при коротких всплесках.rlimit_files
необходим, если у пула много одновременных соединений к базе/файлам; проверьте ulimit сервиса.request_slowlog_timeout
иslowlog
помогут отловить «тяжёлые» точки приложения.
Opcache: быстро, дёшево и почти всегда нужно
Opcache
резко снижает CPU‑нагрузку и ускоряет ответы, особенно на фреймворках/CMF. Он потребляет выделенную память и разделяется между воркерами, поэтому почти всегда выигрывает и по «цена/качество».
[opcache]
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=192
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=100000
opcache.validate_timestamps=1
opcache.revalidate_freq=2
; Для PHP 8 JIT нередко не даёт профита на веб‑нагрузке, но съедает память
opcache.jit=0
opcache.jit_buffer_size=0
Примечания:
opcache.memory_consumption
подбирайте по количеству файлов и размеру кода; при переполнении кэш «дребезжит», растёт латентность.opcache.max_accelerated_files
берите с запасом, чтобы избежать коллизий в хеш‑таблице.- Если используете горячие деплои с частыми изменениями,
validate_timestamps=1
с разумнымrevalidate_freq
— компромисс между скоростью и свежестью кода.
Стабильные таймауты: согласовываем Nginx, PHP и PHP‑FPM
Разнобой таймаутов — источник фантомных 504/502. Золотое правило: таймаут фронтенда должен быть не меньше ожидаемого времени выполнения скрипта плюс небольшой запас. Таймаут на уровне PHP‑FPM должен быть чуть больше таймаута фронтенда, чтобы уметь корректно завершать застрявшие воркеры.
Ключевые параметры:
- PHP‑FPM:
request_terminate_timeout
— принудительно завершает запрос, если он превысил лимит. - PHP:
max_execution_time
— время выполнения скрипта в самом интерпретаторе. - Nginx:
fastcgi_read_timeout
,fastcgi_connect_timeout
,fastcgi_send_timeout
.
Пример согласования: если типовой «тяжёлый» запрос должен укладываться в 55 секунд, выставьте:
- PHP
max_execution_time = 60
. - PHP‑FPM
request_terminate_timeout = 65s
. - Nginx
fastcgi_read_timeout = 70s
.
Фрагмент Nginx‑лока с FastCGI:
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_connect_timeout 5s;
fastcgi_send_timeout 15s;
fastcgi_read_timeout 70s;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
}
Важно: если fastcgi_read_timeout
меньше request_terminate_timeout
, Nginx «устанет» раньше и вернёт 504, в то время как воркер всё ещё работает. Обратная ситуация — тоже плохо: воркер может прервать выполнение, а фронтенд продолжит ждать. Держите таймауты согласованными.
Диагностика: как понять, что упёрлись в лимиты
Признаки и куда смотреть:
- В логах PHP‑FPM: «server reached pm.max_children setting» — очередь запросов, увеличивайте
pm.max_children
или оптимизируйте код/кэширование. - «child exited on signal 9 (SIGKILL) after X seconds» — вероятно, сработал
request_terminate_timeout
; пересмотрите таймауты или оптимизируйте проблемные запросы. - Много записей в slowlog — ищите «узкие места», проверьте индексирование БД, внешние API, файловые операции.
- Пики latencies без роста CPU — упираетесь в I/O (диск/сеть) или ждёте бэкенды.
Включите статус пула и опрашивайте его локально:
# Если FPM слушает Unix‑сокет, curl можно пустить через локальный TCP‑прокси (127.0.0.1 в Nginx)
# Или временно переключите listen на 127.0.0.1:9000 для диагностики.
# Типичные поля pm.status_path:
# accepted conn, listen queue, active processes, idle processes,
# max active processes, max children reached
Ключевые метрики из статуса:
listen queue
— если > 0 и растёт, запросы стоят в очереди, не хватает воркеров.max children reached
— были моменты, когда пул достиг потолка.active/idle processes
— баланс текущей нагрузки.
Сокеты vs TCP, backlog и spawn rate
Unix‑сокеты обычно быстрее и экономичнее TCP на локальном хосте. Убедитесь, что права на listen
позволяют Nginx подключаться к сокету, а listen.backlog
достаточно велик, чтобы сгладить всплески. Для pm = dynamic
при интенсивных пиках проверьте pm.max_spawn_rate
— он ограничивает скорость появления новых воркеров. Если спавн идёт слишком медленно, кратковременные очереди возможны даже при достаточном pm.max_children
.
Практические заметки про memory_limit и max_requests
memory_limit
в PHP — это потолок памяти на один запрос, а не реальный расход воркера «на холостых». Если у вас есть редкие, но тяжёлые запросы, не завышайте memory_limit
«с запасом» — лучше оптимизировать код или вынести тяжёлую работу в очереди/воркеры. pm.max_requests
помогает сбрасывать фрагментацию памяти и утечки в расширениях: если видите постепенный рост RSS у воркеров, уменьшите значение, например до 500–1000.
Профили конфигураций: быстрый старт
Профиль «стабильный трафик, средний онлайн»
Выбирайте pm = dynamic
, считайте pm.max_children
по методике, выставляйте start/min/max_spare
на уровне 30/20/60% от потолка, max_requests
1000, request_terminate_timeout
синхронизируйте с Nginx. Opcache
включён, объём подбирается по статусу кеша.
Профиль «редкие всплески, экономия памяти»
pm = ondemand
, агрессивный pm.process_idle_timeout
10–20s, холодные старты прогревайте заранее (скриптом/кеш‑праймером), max_children
не урезайте слишком сильно — всплески всё равно потребуют воркеров.
Профиль «низкая латентность, предсказуемость»
pm = static
, фиксированное число воркеров строго по памяти, max_requests
500–1000, таймауты выставлены с запасом. Такой профиль хорошо сочетается с внешней балансировкой по нескольким бэкендам.
Чек‑лист перед выкатыванием
- Сняли PSS воркеров под реалистичной нагрузкой и посчитали
pm.max_children
. - Режим
pm
выбран под профиль трафика;start/min/max_spare
настроены дляdynamic
илиprocess_idle_timeout
дляondemand
. Opcache
включён, «не краснеет» по заполнению в статусе (нет реконструкций кеша).- Согласованы
max_execution_time
,request_terminate_timeout
иfastcgi_read_timeout
. - Проверены
listen.backlog
, права на сокет иrlimit_files
. - Включены
slowlog
иpm.status_path
для наблюдения; доступ только локально. - Перезагрузка без даунтайма: проверка конфигов и мягкий reload.
# Проверка и перезапуск безопасно
php-fpm -t && \
\
nginx -t && \
\
systemctl reload php-fpm && \
\
systemctl reload nginx
Итог
Дисциплина в настройках PHP‑FPM — это не «магические» значения из интернета, а метод: измерили PSS воркеров, посчитали pm.max_children
под свой бюджет памяти, выбрали режим pm
под профиль трафика, включили и настроили Opcache
, согласовали таймауты в Nginx/PHP/FPM, подключили статус и slowlog. Такой подход даёт предсказуемую латентность, меньше 502/504 и более ровную загрузку VDS. Дальше — инженерия приложений: кеши, индексы, профилирование. Для сайтов на общем тарифе см. заметки про кэширование и ускорение PHP: Opcache и сжатие.