Пятница, вечер, отдел продаж массово выгружает отчёты перед закрытием месяца — и портал начинает отдавать 502 через раз. В логе php-fpm при этом одна и та же строка: server reached pm.max_children setting, consider raising it. Классическая история: сервер настраивали по гайду для тестового стенда, а под продакшен-нагрузку параметры пула никто не пересчитывал. Ниже — как настроить Nginx и PHP-FPM для коробочного Битрикс24 так, чтобы это не повторялось: версии PHP, расчёт пула под объём RAM, схема связки с Apache или без неё, кэш статики и разбор, почему вообще возникают 502 и 504.
Материал для разработчика или админа, который уже поднял портал (сам или через BitrixEnv) и теперь донастраивает его под реальную нагрузку, а не под установочный мастер.
Что понадобится
- Доступ к серверу по SSH с правами root или sudo.
- Уже установленный коробочный Битрикс24 — через BitrixEnv или руками. Если портала ещё нет, сначала разверните его на облачном сервере, это отдельная задача.
- Информация о ресурсах сервера: количество ядер, объём RAM, тип диска. Без этих цифр расчёт пула — гадание.
- Понимание, сколько человек одновременно работает в портале и есть ли внешняя нагрузка (сайт на 1С-Битрикс, вебхуки, интеграции, REST API от внешних систем).
- Доступ к конфигурации PHP-FPM и Nginx. В BitrixEnv это
/etc/nginx/bx/и пул в/etc/opt/php83/php-fpm.d/(путь зависит от версии PHP, устанавливаемой через BitrixEnv).
Настройка Nginx и PHP-FPM для коробочного Битрикс24 в продакшене: разбираем по шагам
Шаг 1. Выбираем схему: nginx + apache или nginx + php-fpm напрямую
BitrixEnv по умолчанию ставит связку nginx-фронт + Apache-бэкенд с mod_php: nginx отдаёт статику и проксирует динамику на Apache, который уже выполняет PHP-код. Это рабочая и поддерживаемая схема, и трогать её без причины не обязательно — она хорошо интегрирована в меню /root/menu.sh, и обновления окружения не ломают то, что не тронуто руками.
Вторая схема — Nginx напрямую перед PHP-FPM, без Apache. Она быстрее под нагрузкой: меньше слоёв, ниже расход памяти на процесс (у Apache prefork каждый воркер тяжелее, чем worker php-fpm), проще диагностика — один лог вместо двух. Такой вариант встречается и в материалах вендора, но это не операция из стандартного меню BitrixEnv — конфиги придётся вести руками, и здесь та же ловушка, что и с любой ручной правкой: следующая операция в меню (например, смена сайта или перевыпуск сертификата) может перегенерировать nginx-конфиг и затереть кастомные location.
Наш подход: если сервер один и им управляют через BitrixEnv — оставляем Apache-бэкенд, но переносим его на PHP-FPM вместо mod_php (Apache 2.4 с mod_proxy_fcgi это умеет), выигрывая по памяти без отказа от привычного меню. Если сервер выделенный, под высокую нагрузку, и конфиги ведутся в git — переходим на чистую связку nginx + php-fpm. Дальше показываю оба пути, различия — только в звене между nginx и PHP.

Шаг 2. Ставим PHP нужной версии
Вендор регулярно поднимает минимальную поддерживаемую версию PHP. Портал на версии ниже минимальной продолжит открываться, но часть модулей начнёт показывать предупреждения в проверке системы, а обновления перестанут рассчитывать на ваш движок.
Практическое правило: не берите самую свежую версию PHP в день её выхода. Поддержка новых веток у продукта появляется с задержкой, а кастомные модули проверяются на совместимость ещё позже. Разумный выбор — предпоследняя поддерживаемая версия, обкатанная на тестовом контуре.
Через BitrixEnv версия PHP ставится и переключается через /root/menu.sh → пункт управления версиями PHP; окружение само разводит несколько версий по путям вида /opt/php83, /opt/php84, чтобы можно было тестировать новую версию, не ломая текущий сайт.
Обязательные для продакшена настройки в php.ini:
; Отключаем проверку времени модификации файлов на каждый запрос —
; в продакшене код не меняется "на лету", это чистый выигрыш по CPU
opcache.validate_timestamps = 0
; Память под закэшированный байт-код. Для Битрикс24 с его объёмом кода
; 256M — комфортный минимум, на проекте с кастомными модулями поднимайте до 512M
opcache.memory_consumption = 256
; Число файлов, которые может держать в кэше opcache.
; У коробки легко набирается 15-20 тысяч php-файлов с учётом модулей
opcache.max_accelerated_files = 30000
; Кэш резолвинга путей — портал активно ходит по include-цепочкам
realpath_cache_size = 4096K
realpath_cache_ttl = 600
; Лимит памяти на процесс. 256M хватает на большинство страниц,
; тяжёлые отчёты и импорт CRM могут требовать 512M
memory_limit = 256M
; Время выполнения обычного запроса. Для fpm-процессов, которые
; висят дольше (агенты, экспорт) — отдельный пул с большим значением
max_execution_time = 60
При opcache.validate_timestamps = 0 не забывайте, что после деплоя кода нужно сбрасывать кэш (opcache_reset() через админку или перезапуск php-fpm) — иначе увидите старую версию файлов ещё несколько часов.

Шаг 3. Настраиваем пул PHP-FPM: расчёт pm.max_children под RAM
Это узкое место, из-за которого чаще всего ловят 502. По умолчанию во многих дистрибутивах pm.max_children стоит на символических 5, что для одного разработчика достаточно, а для десяти сотрудников — нет.
Логика простая: сколько параллельных PHP-процессов может выдержать оставшаяся память сервера, столько и ставим. Формула:
pm.max_children = (RAM_доступная_под_php_fpm) / (средний_размер_процесса)
RAM под php-fpm — это не вся память сервера, а то, что останется после MySQL (innodb_buffer_pool_size), memcached/Redis, push-сервера, nginx/apache и системных нужд. Средний размер процесса php-fpm под коробкой обычно 50-90 МБ RSS — админка и CRM тяжелее типовой страницы каталога, берите верхнюю границу для запаса.
Пример для сервера на 8 ГБ RAM (10-15 человек): MySQL — 2,5 ГБ, memcached и push — 0,5 ГБ, nginx/система — 1 ГБ, остаётся около 4 ГБ под php-fpm. При среднем процессе 70 МБ получаем 4096 / 70 ≈ 58. Ставим с запасом вниз, не под самый край:
; Пул для основного сайта портала, файл вида
; /etc/opt/php83/php-fpm.d/www.conf или отдельный пул в conf.d/bitrix24.conf
[bitrix24]
user = bitrix
group = bitrix
listen = /var/run/php-fpm/bitrix24.sock
listen.owner = nginx
listen.group = nginx
; dynamic — процессы плодятся и схлопываются по нагрузке,
; для продакшена это разумный баланс между памятью и откликом
pm = dynamic
; Верхний предел параллельных процессов — по расчёту выше
pm.max_children = 50
; Сколько процессов держим постоянно живыми, не пересоздавая
pm.start_servers = 10
pm.min_spare_servers = 8
pm.max_spare_servers = 20
; После скольки запросов процесс перезапускается —
; защита от накопления памяти при утечках в коде модулей
pm.max_requests = 500
; Таймаут, после которого fpm сам убьёт зависший запрос
; и запишет его в slowlog — держите чуть меньше nginx-таймаута
request_terminate_timeout = 60
request_slowlog_timeout = 10
slowlog = /var/log/php-fpm/bitrix24-slow.log
; Отдельный статус-эндпоинт для мониторинга загрузки пула
pm.status_path = /fpm-status
pm.max_children = 50 — это не универсальная цифра, а результат расчёта под конкретные 8 ГБ и конкретную нагрузку. На 16 ГБ с тем же профилем нагрузки получится порядка 100-120, но проверяйте по факту через pm.status_path, а не только по формуле — реальный размер процесса скачет в зависимости от того, какие модули и REST-обработчики активны.
Частая ошибка на этом шаге: поднять pm.max_children до заоблачных значений, чтобы «уж точно хватило», без пересчёта под RAM. Сервер уходит в своп при пиковой нагрузке, и вместо быстрого 502 вы получаете вариант хуже — портал висит по 30-60 секунд, а потом всё равно падает в 504.

Шаг 4. Настраиваем Nginx как фронт
Если сохраняете связку с Apache — nginx только проксирует динамику на Apache и отдаёт статику сам, это конфигурация BitrixEnv по умолчанию, трогать её незачем. Если переходите на прямую связку с php-fpm, ключевой блок такой:
# Апстрим на unix-сокет php-fpm, отдельный пул под Битрикс24
upstream php_bitrix24 {
server unix:/var/run/php-fpm/bitrix24.sock;
}
server {
listen 443 ssl;
http2 on; # с nginx 1.25.1 http2 включается отдельной директивой,
# а не параметром listen — старый синтаксис пишет предупреждение в лог
server_name portal.company.ru;
root /home/bitrix/www;
index index.php;
# Сертификат подключаем через шаблоны BitrixEnv, не переопределяем вручную
include /etc/nginx/bx/ssl.conf;
location / {
try_files $uri $uri/ /bitrix/urlrewrite.php$is_args$args;
}
location ~ \.php$ {
fastcgi_pass php_bitrix24;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# Таймаут ожидания ответа от php-fpm — держим чуть больше
# request_terminate_timeout из пула, иначе nginx оборвёт раньше fpm
fastcgi_read_timeout 65;
fastcgi_send_timeout 65;
# Клиент мог закрыть вкладку — не убиваем скрипт из-за этого,
# актуально для длинных импортов и бизнес-процессов
fastcgi_ignore_client_abort on;
}
# Статус пула php-fpm — закрываем от внешнего доступа
location = /fpm-status {
allow 127.0.0.1;
deny all;
fastcgi_pass php_bitrix24;
include fastcgi_params;
# без SCRIPT_FILENAME статус не отдаётся: php-fpm не понимает,
# что от него хотят, и возвращает "Access denied" или пустой ответ
fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
}
}
Обратите внимание на fastcgi_ignore_client_abort on — без него разрыв соединения на стороне клиента (человек закрыл вкладку, пока грузился длинный отчёт) моментально убивает php-процесс, и фоновая операция, которая должна была доработать, обрывается на середине.
Шаг 5. Настраиваем кэширование статики
Каталог /bitrix/js, /bitrix/css, /upload, /bitrix/templates — это то, что нагружает канал, но не должно каждый раз идти через php вообще, и даже через nginx лишний раз ходить незачем:
location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2?|ttf|ico)$ {
root /home/bitrix/www;
# Браузер и CDN кэшируют файл на 30 дней —
# у Битрикс24 в путях статики есть версионирование (?v=...),
# поэтому долгий кэш не приводит к показу устаревшего файла
expires 30d;
add_header Cache-Control "public, immutable";
# Не пишем в access_log каждый запрос картинки —
# на нагруженном портале это ощутимая экономия дисковых IOPS
access_log off;
# gzip для текстовых форматов статики
gzip_static on;
}
Если у вас включено ускорение сайта (композитный кэш) в настройках производительности портала, анонимные посетители внешнего сайта на этом же движке вообще не доходят до PHP — nginx отдаёт готовый HTML из кэша композита. Для внутреннего портала Битрикс24 это не так критично: сотрудники всегда авторизованы, композит на них не работает, и всю нагрузку по-прежнему держит пул php-fpm.
Частые ошибки и как их избежать
502 Bad Gateway при заходе нескольких человек одновременно. Пул php-fpm исчерпан: все pm.max_children заняты, новые запросы получают отказ в соединении. В логе php-fpm ищите server reached pm.max_children setting, consider raising it — если строка есть, лечится либо пересчётом пула под RAM (шаг выше), либо поиском, что именно держит процессы дольше нормы (медленный внешний API, необновлённая версия модуля с известной утечкой).
504 Gateway Timeout на тяжёлых операциях. fastcgi_read_timeout в nginx меньше, чем реально нужно операции — экспорту, синхронизации с 1С, тяжёлому отчёту. Nginx обрывает соединение раньше, чем php успевает досчитать. Решение — не задирать таймаут глобально, а вынести тяжёлые location (например, /bitrix/admin/, ваш REST-обработчик синхронизации) в отдельный location с увеличенным fastcgi_read_timeout, оставив короткий таймаут для обычных страниц.
Портал работает нормально днём и тормозит по вечерам, когда все выгружают отчёты. Это тот же дефицит пула, но проявляется не как жёсткий 502, а как рост времени ответа: свободные pm.max_children заканчиваются, запросы встают в очередь на уровне сокета. Диагностируется через pm.status_path — смотрите на listen queue и max children reached, если счётчик растёт — пул мал для этой нагрузки.
После перехода на php-fpm выросло число ошибок подключения к MySQL. Каждый php-fpm процесс может держать соединение с базой, и увеличенный pm.max_children умножает нагрузку на max_connections в MySQL. Расчёт пула под RAM должен учитывать не только память php, но и то, что MySQL тоже может упереться в лимит соединений при резком росте числа воркеров.
Из практики А2
На одном из проектов сервер под коробку выдавали через инфраструктурный отдел клиента — 4 ядра, 16 ГБ RAM, конфигурация приличная. Но пул php-fpm стоял с дефолтными настройками дистрибутива, pm.max_children = 5. Портал на 8 человек работал терпимо, потому что редко когда пять человек одновременно грузили страницу с тяжёлым отчётом. Проблема вылезла ровно в момент подключения интеграции с внешней 1С через REST API: каждый синхронизационный запрос занимал php-процесс на несколько секунд, и уже при трёх параллельных запросах от 1С плюс двух активных сотрудниках пул забивался полностью — сотрудники получали 502 в моменты синхронизации.
Пересчитали пул под фактическую память (после вычета MySQL и push-сервера осталось около 9 ГБ), подняли pm.max_children до 90 и вынесли обработчик REST-синхронизации в отдельный пул с меньшим pm.max_requests, чтобы не смешивать долгие интеграционные запросы с обычными пользовательскими — так падение одного пула не задевало второй.
Отдельно всплыла деталь с opcache.validate_timestamps: в первой конфигурации его оставили включённым «на всякий случай», и при активной разработке (в проекте шли доработки прямо на проде — не лучшая практика, но так было) каждый php-запрос дополнительно стучался в файловую систему проверять время модификации сотен файлов. На SSD это было незаметно поодиночке, но при возросшем числе параллельных процессов давало ощутимую просадку по CPU. Отключили timestamp-проверку и добавили в процесс деплоя явный сброс opcache — время отклика админки заметно выровнялось.
Ещё одно наблюдение: клиенты часто путают связку «nginx перед Apache» с «nginx вместо Apache» и просят убрать Apache просто ради красивой схемы в презентации. Если сервер и так не упирается в память, а конфиги ведутся через меню BitrixEnv — миграция на чистый php-fpm ради экономии 200-300 МБ памяти того не стоит, риск сломать что-то в момент перехода выше выгоды. Смысл появляется, когда сервер реально упирается в ресурсы или когда нужна тонкая настройка таймаутов по отдельным location, которую через Apache делать неудобно.
Итог
После этой настройки у вас пул PHP-FPM, рассчитанный под реальный объём памяти сервера, а не под дефолтные значения дистрибутива, Nginx с кэшируемой статикой и разумными таймаутами, и понимание, откуда берутся 502 и 504 под нагрузкой. Дальше стоит понаблюдать за pm.status_path в течение недели-двух в реальной работе и подкорректировать pm.max_children по факту — формула даёт стартовую точку, а не окончательное число. Если на каком-то шаге застряли — опишите ситуацию в комментарии или напишите нам напрямую.