Top.Mail.Ru
OSEN-НИЙ SAAALEСкидка 50% на виртуальный хостинг и VDS
до 30.11.2025 Подробнее
Выберите продукт

Тюнинг PHP‑FPM на VDS: pm.*, max_children, память и стабильные таймауты

Разбираем, как устроен PHP‑FPM и как добиться предсказуемой производительности на VDS. Пошагово настраиваем pm dynamic/ondemand, считаем pm.max_children, уменьшаем memory footprint, настраиваем Opcache и синхронизируем таймауты Nginx/PHP/FPM для предсказуемых ответов без 502/504.
Тюнинг PHP‑FPM на VDS: pm.*, max_children, память и стабильные таймауты

Если сайт на 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 и т. п.).

Замер PSS воркеров PHP‑FPM под нагрузкой с помощью smem

Расчёт pm.max_children: методика и пример

Формула простая: из доступной памяти вычесть резервы под ОС и фоновые сервисы, вычесть общий сегмент Opcache и прочие «глобальные» потребители, остаток разделить на медианный PSS одного воркера и округлить вниз.

Шаги:

  1. Определите «бюджет» памяти: общая RAM минус резерв ОС (обычно 10–20% для дискового кеша и ядра).
  2. Вычтите память под Nginx, базу данных, Redis/мессенджеры и прочие компоненты.
  3. Вычтите объём Opcache (opcache.memory_consumption + накладные).
  4. Оставшийся объём разделите на 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, запас лучше увеличить.

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

Режимы 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 для стабильных ответов

Стабильные таймауты: согласовываем 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 и сжатие.

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

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

PHP-настройки на виртуальном хостинге: .user.ini, php_value и безопасные лимиты OpenAI Статья написана AI Fastfox

PHP-настройки на виртуальном хостинге: .user.ini, php_value и безопасные лимиты

Как на виртуальном хостинге корректно повысить memory_limit, upload_max_filesize и другие директивы PHP? Что выбрать — .user.ini и ...
Точечное восстановление MySQL/MariaDB: binlog, GTID и контроль точек OpenAI Статья написана AI Fastfox

Точечное восстановление MySQL/MariaDB: binlog, GTID и контроль точек

Точечное восстановление (PITR) спасает от случайных DROP/UPDATE и багов релизов. Разберём, как подготовить MySQL/MariaDB для безоп ...
PITR для PostgreSQL: архивация WAL, base backup и тест восстановления OpenAI Статья написана AI Fastfox

PITR для PostgreSQL: архивация WAL, base backup и тест восстановления

Практическое руководство для админов и DevOps: как включить архивацию WAL в PostgreSQL, корректно снимать base backup, удерживать ...