PHP-FPM pm.max_children: параметр, который кладёт Bitrix под нагрузкой
Bitrix выдаёт 504. Nginx логи чистые. MySQL загружен на 40%. OPcache прогрет. Composite cache включён. И всё равно сайт мёртв в час пик.
Я видел этот сценарий в нескольких проектах. Каждый раз диагностика занимала слишком долго, потому что мы начинали не с того конца. Начинали с кода, с базы, с сетью — а нужно было начинать с конфига PHP-FPM.
Конкретный случай: интернет-магазин, акция, трафик вырос в три раза. TTFB улетел на 8-12 секунд. ~600 ошибок 504 в час. ps aux | grep php-fpm показывал ровно 5 процессов. Все пять заняты. Запросы в очередь — и там умирают по таймауту.
Параметр в конфиге: pm.max_children = 5. Дефолт ISPmanager. Для продакшн-Bitrix с каталогом от 10 000 SKU это потолок, который сработает раньше, чем ожидаешь.
Как PHP-FPM принимает запросы: очередь и воркеры
PHP-FPM работает по модели пула процессов. Nginx передаёт PHP-запросы через FastCGI-сокет, FPM помещает их в очередь. Свободный воркер берёт запрос из очереди и обрабатывает. Когда свободных воркеров нет — новые запросы ждут.
pm.max_children — это максимальное количество параллельных PHP-процессов в пуле. Если все max_children заняты, следующий запрос встаёт в очередь. Длина очереди управляется listen.backlog (по умолчанию 511). Когда и очередь переполнена — FPM начинает отказывать.
На большинстве VPS через ISPmanager или VestaCP этот параметр стоит 5. Это значение из дефолтных шаблонов панели, которое никто не меняет при выкатке в продакшн.
502 vs 504 — в чём разница
Оба кода говорят, что Nginx не дождался ответа от PHP-FPM, но причины разные.
504 Gateway Timeout — запрос ушёл в PHP-FPM, воркер начал его обрабатывать, но не уложился в fastcgi_read_timeout. Это либо медленный запрос (тяжёлый SQL, внешний API), либо воркер завис.
502 Bad Gateway — Nginx вообще не смог подключиться к FPM или получил немедленный отказ. Это случается, когда очередь переполнена и FPM сбрасывает соединение, либо процесс FPM упал.
На практике: если под нагрузкой видишь 504 — смотришь на время обработки запросов и очередь. Если 502 — на состояние самого FPM-процесса.
В нашем случае были именно 504: воркеры работали, но пять запросов одновременно обрабатывались по 2-3 секунды каждый (Bitrix с каталогом без composite на некэшированных страницах), следующие ждали, таймаут наступал раньше, чем очередь рассасывалась.
Формула расчёта pm.max_children для Bitrix
Формула простая:
pm.max_children = (RAM_total - RAM_OS - RAM_MySQL) / RAM_per_php_process
Средний PHP-FPM процесс Bitrix с prolog/epilog занимает 50-120 MB RAM. На сайте с включёнными модулями (sale, catalog, iblock) без оптимизации — ближе к 80-100 MB. Проверяйте своё значение через ps aux --sort -rss | grep php-fpm.
Пример для VPS 4 GB RAM:
- Система + Nginx: ~512 MB
- MySQL: ~1024 MB (конфиг innodb_buffer_pool_size 768 MB + overhead)
- Доступно PHP: 4096 - 512 - 1024 = 2560 MB
- Процесс Bitrix PHP: ~68 MB (среднее по замеру)
pm.max_children = 2560 / 68 ≈ 37
Мы поставили 38. TTFB под нагрузкой упал с 8-12 секунд до 1.1 секунды. Ошибки 504 исчезли за 5 минут после применения конфига.
Важно оставить запас на случай, что процессы раздуются. Не берите 100% доступной памяти — оставляйте 10-15% на рост. При pm.max_children = 38 и 68 MB на процесс пиковое потребление PHP ≈ 2.6 GB, что укладывается с запасом.
pm.dynamic vs pm.static — что выбирать для Bitrix
Три режима управления пулом.
Первый — pm.static: фиксированное количество воркеров, все запущены всегда. Предсказуемо, минус — память занята постоянно, даже ночью.
Второй — pm.dynamic: воркеры создаются и убиваются по необходимости. Параметры: pm.max_children, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers. Экономит RAM в периоды низкой нагрузки.
Третий — pm.ondemand: процесс создаётся только под входящий запрос. Для продакшна не подходит — первый запрос после паузы получает overhead на создание процесса.
Для продакшн-Bitrix средней нагрузки я использую pm.dynamic со следующими значениями (пример для VPS 4 GB, max_children=38):
pm = dynamic
pm.max_children = 38
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 500
pm.max_requests = 500 ограничивает жизнь воркера количеством обработанных запросов — помогает с утечками памяти в старых версиях Bitrix.
Если RAM больше 8 GB и нагрузка стабильная — pm.static с max_children = 60-80 даёт более предсказуемый результат.
Мониторинг пула без внешних агентов
PHP-FPM умеет отдавать статус через встроенный endpoint. Включается в www.conf:
pm.status_path = /fpm-status
Затем в Nginx добавляется location:
location = /fpm-status {
allow 127.0.0.1;
deny all;
fastcgi_pass unix:/var/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
После этого curl http://localhost/fpm-status показывает:
pool: www
process manager: dynamic
start time: 01/Sep/2024:12:00:00 +0000
accepted conn: 12847
listen queue: 0
max listen queue: 127
idle processes: 8
active processes: 3
total processes: 11
max active processes: 38
listen queue больше нуля — запросы ждут. active processes близко к pm.max_children — пул на пределе. На это нужно ставить алерт.
В нашем случае перед инцидентом listen queue доходил до 127 при listen.backlog по умолчанию. Это была очередь из 127 запросов, которые либо дождались воркера, либо получили 504.
Что не менять вместе с pm.max_children
fastcgi_read_timeout в Nginx — отдельный параметр. Иногда его увеличивают, пытаясь «вылечить» 504. Это маскирует проблему: запросы будут ждать дольше, очередь вырастет, память закончится раньше. Сначала правьте пул — потом смотрите, остались ли медленные запросы.
memory_limit в php.ini не влияет на размер FPM-процесса напрямую. Он ограничивает память на один запрос, но сам процесс занимает больше из-за загруженных модулей Bitrix. Замеряйте через ps aux, не через memory_limit.
Итог
Дефолт pm.max_children = 5 от ISPmanager — это не баг и не саботаж. Это настройка для shared-хостинга, где ресурсы между сайтами делятся. На выделенном VPS с Bitrix-каталогом она работает только в спокойное время.
Формула займёт 10 минут. Применение конфига — перезапуск FPM без даунтайма (systemctl reload php8.1-fpm). Мониторинг через /fpm-status займёт ещё 15 минут.
Следующие вещи, которые стоит проверить на том же сервере — в статьях Как PHP OPcache убивает production Bitrix и Файловые сессии PHP убили наш сервер в 23:00. Все три проблемы из одного слоя, и чинятся одним аудитом конфига за полдня.