Оптимизация PHP-FPM: pm.max_children и пулы под вашу RAM

Сайты и веб-серверы
Оптимизация PHP-FPM: pm.max_children и пулы под вашу RAM

Настройка PHP-FPM — самый недооценённый этап после установки LEMP-стека. Дефолтный конфиг Ubuntu 24.04 разрешает всего 5 воркеров (pm.max_children = 5): на сервере с 16 ГБ RAM сайт будет «тормозить» при 6 одновременных запросах, хотя ресурсы простаивают. Обратная ошибка — задрать лимит наугад и словить OOM-killer в первый же пик трафика. В этой статье разберём оптимизацию PHP-FPM по-инженерному: как замерить память воркера, посчитать pm.max_children под конкретный объём RAM, выбрать режим менеджера процессов и разнести сайты по пулам. Все примеры — для PHP 8.3 на Ubuntu 24.04 LTS.

Как устроен PHP-FPM: мастер и воркеры

PHP-FPM (FastCGI Process Manager) — это служба, которая держит пул готовых PHP-процессов. Nginx передаёт запрос в сокет, свободный воркер исполняет скрипт и возвращает ответ. Ключевые файлы на Ubuntu 24.04:

  • /etc/php/8.3/fpm/php-fpm.conf — глобальный конфиг службы;
  • /etc/php/8.3/fpm/pool.d/www.conf — конфиг пула по умолчанию (здесь и живёт pm.max_children);
  • /etc/php/8.3/fpm/php.ini — настройки самого PHP (memory_limit, OPcache).

Каждый воркер — отдельный процесс, занимающий десятки мегабайт RAM. Отсюда главный принцип тюнинга: количество воркеров должно соответствовать памяти сервера, а не взятой с потолка цифре. Если LEMP у вас ещё не развёрнут, начните с инструкции по установке LEMP-стека на Ubuntu.

Шаг 1. Замеряем память одного воркера

Под рабочей нагрузкой (или после прогулки по сайту) выполните:

ps --no-headers -o rss -C php-fpm8.3 | awk '{s+=$1; n++} END {print s/n/1024 " MB"}'

Команда выведет средний RSS процесса PHP-FPM в мегабайтах. Ориентиры: WordPress — 60–90 МБ, Laravel — 40–80 МБ, WooCommerce и «тяжёлые» магазины — 120+ МБ. Мастер-процесс тоже попадёт в выборку, но на средних значениях это почти не сказывается.

Шаг 2. Считаем pm.max_children под вашу RAM

Формула простая:

pm.max_children = (RAM_для_PHP) / (средний_размер_воркера)

RAM_для_PHP — это не вся память сервера. Вычтите то, что занимают соседи: MySQL (обычно 1–4 ГБ), Nginx (50–100 МБ), Redis, сама система (~500 МБ). Пример для тарифа Pro у Cheap-Host (12 vCPU, 16 ГБ RAM, 750 ₽/мес):

  • 16 ГБ всего − 3 ГБ MySQL − 1 ГБ Redis и система − 1 ГБ запас = 11 ГБ на PHP;
  • средний воркер WordPress — 80 МБ;
  • 11 264 / 80 ≈ 140 → с запасом ставим pm.max_children = 120.

Для сервера с 8 ГБ (Start, 450 ₽/мес) по той же логике получится 45–60 воркеров. Подробнее о распределении памяти между сервисами — в статье сколько оперативной памяти нужно серверу.

Шаг 3. Выбираем режим менеджера процессов

РежимКак работаетКогда выбирать
staticВсегда держит ровно max_children процессовОдин нагруженный сайт, память выделена только под PHP: нет накладных расходов на форки
dynamicДержит «запасные» процессы, масштабируется между min и maxТипичный VPS, где PHP соседствует с MySQL. Разумный дефолт
ondemandЗапускает воркеры только при запросах, убивает после простояМного маленьких сайтов/пулов с редкими посещениями — экономит RAM в простое

Рабочий пример для dynamic на 16 ГБ RAM — правим /etc/php/8.3/fpm/pool.d/www.conf:

pm = dynamic
pm.max_children = 120
pm.start_servers = 24
pm.min_spare_servers = 12
pm.max_spare_servers = 36
pm.max_requests = 500

Правила согласованности для dynamic: start_servers должен лежать между min_spare_servers и max_spare_servers, иначе FPM не стартует. Классическая пропорция: start = 20% от max_children, min_spare = 10%, max_spare = 30%.

pm.max_requests = 500 — страховка от утечек памяти: каждый воркер после 500 запросов перезапускается. Для стабильных приложений можно 1000, для «текущих» легаси — 200.

Шаг 4. Полезные параметры, о которых забывают

  • Slow log. Показывает трейсы скриптов, работающих дольше порога:
  slowlog = /var/log/php8.3-fpm-slow.log
  request_slowlog_timeout = 5s
  • Жёсткий таймаут. request_terminate_timeout = 60s — убивает зависшие запросы, чтобы они не занимали воркеры вечно.
  • Страница статуса. В пуле: pm.status_path = /fpm-status, а в Nginx откройте её только с localhost. В выводе смотрите active processes, max children reached (должно быть 0) и listen queue.
  • OPcache. В php.ini убедитесь, что включён кеш байткода — он ускоряет PHP в 2–3 раза без единой правки кода:
  opcache.enable=1
  opcache.memory_consumption=256
  opcache.max_accelerated_files=20000
  opcache.validate_timestamps=1

После любых правок — проверка синтаксиса и перезапуск:

sudo php-fpm8.3 -t
sudo systemctl restart php8.3-fpm

Отдельные пулы под каждый сайт

Если на сервере несколько проектов, не сваливайте их в общий пул www. Отдельный пул на сайт даёт изоляцию по памяти, отдельного системного пользователя (взлом одного сайта не даёт доступ к соседям) и индивидуальные лимиты. Создаём /etc/php/8.3/fpm/pool.d/shop.conf:

[shop]
user = shop
group = shop
listen = /run/php/php8.3-fpm-shop.sock
listen.owner = www-data
listen.group = www-data
pm = ondemand
pm.max_children = 30
pm.process_idle_timeout = 10s
pm.max_requests = 500
php_admin_value[memory_limit] = 256M
php_admin_value[open_basedir] = /var/www/shop:/tmp

В server-блоке Nginx этого сайта меняем сокет: fastcgi_pass unix:/run/php/php8.3-fpm-shop.sock;. Сумма max_children всех пулов по-прежнему должна укладываться в доступную RAM — считайте по худшему случаю. Схема раскладки нескольких проектов по одному серверу разобрана в статье про несколько сайтов на одном VPS.

Как понять, что настройка сработала

  1. sudo grep max_children /var/log/php8.3-fpm.log — предупреждений «server reached pm.max_children» быть не должно;
  2. free -h под нагрузкой — swap не растёт, есть свободная память;
  3. страница /fpm-statuslisten queue = 0, max children reached = 0;
  4. время ответа (TTFB) в пиках не деградирует.

Дальше узкое место обычно смещается в базу данных и отсутствие кеша — тут поможет Redis для кеширования: он снимает с PHP и MySQL значительную часть повторяющейся работы.

И честное замечание: тюнинг не заменяет ресурсы. Если воркеров объективно не хватает, нужен сервер с большей RAM. У Cheap-Host апгрейд тарифа выполняется без потери данных, а KVM-виртуализация гарантирует, что выделенные гигабайты действительно ваши, — сравнить конфигурации можно на странице тарифов.

FAQ

Что будет, если pm.max_children слишком маленький?

Запросы встают в очередь: сайт «подвисает» под наплывом посетителей, в логе PHP-FPM появляются предупреждения server reached pm.max_children, при этом память сервера может быть наполовину свободна.

Что будет, если pm.max_children слишком большой?

Под пиковой нагрузкой воркеры съедят всю RAM, сервер уйдёт в swap или OOM-killer начнёт убивать процессы — обычно первым погибает MySQL. Поэтому лимит считают от реальной памяти, а не «на глаз».

Какой режим pm выбрать: static, dynamic или ondemand?

Для нагруженного сайта на выделенном под PHP сервере — static. Для типичного VPS с сайтом и базой — dynamic. Для множества маленьких сайтов с редкими визитами — ondemand.

Как узнать, сколько памяти ест один процесс PHP-FPM?

Под нагрузкой: ps --no-headers -o rss -C php-fpm8.3 | awk '{s+=$1; n++} END {print s/n/1024 " MB"}'. Для WordPress типичны 60–90 МБ, для Laravel — 40–80 МБ.

Нужно ли перезапускать PHP-FPM после изменения конфига?

Да. Сначала проверьте синтаксис (php-fpm8.3 -t), затем systemctl restart php8.3-fpm — или reload, если не хотите обрывать текущие запросы.

Вывод

Оптимизация PHP-FPM — это 30 минут работы: замерить воркер, посчитать pm.max_children от доступной RAM, выбрать режим pm, включить max_requests, slow log и OPcache. Результат — сервер, который держит пики трафика без очередей и не падает от нехватки памяти. Если под ваш проект нужна платформа с запасом по RAM — у Cheap-Host тариф Pro за 750 ₽/мес: 12 vCPU, 16 ГБ RAM, 128 ГБ NVMe, безлимитный трафик и выдача сервера за минуту на cheap-host.onl.

Читайте также