Битрикс24: отдельный PHP-FPM пул для IMAP — почему модуль «Почта» тормозит весь сервер
Битрикс синхронизация почты часто становится скрытой причиной деградации производительности коробочного портала. Днём система работает стабильно, но в часы обновления ящиков административная панель перестаёт отвечать, а пользователи сталкиваются с долгими загрузками страниц и ошибками 502. При этом мониторинг показывает низкую нагрузку на процессор и базу данных, что сбивает с толку при первичной диагностике.
Корень проблемы кроется в архитектуре PHP-FPM: воркеры общего пула массово блокируются на чтении IMAP-сокетов и перестают обслуживать обычные запросы. Изоляция скрипта check_mail.php в отдельный пул с жёсткими лимитами времени решает конфликт ресурсов без дорогостоящего апгрейда сервера. Это один из разборов большой темы — почему тормозит коробочный Битрикс24; смежный технический случай из того же кластера — почему не перематывается запись звонка.

Изоляция скрипта check_mail.php в отдельный небольшой PHP-FPM пул с жёстким таймаутом устраняет блокировку основного сайта при синхронизации IMAP, предотвращая ошибки 502 и задержки отдачи страниц.
Содержание
Симптом: портал встаёт в часы синхронизации почты
Симптом коварен тем, что мониторинг показывает спокойную картину: процессор холодный, диск свободен, MySQL без длинных запросов, swap не используется. «Добавьте RAM» или «оптимизируйте индексы» здесь не помогут — узкое место не в ресурсах.
По нашему опыту на коробочных Битриксах с десятком подключённых ящиков и медленно отвечающим внешним IMAP симптом проявляется каждое утро: с запуском агентов синхронизации портал отдаёт страницы с задержкой, иногда 502; после обеда отпускает, когда часть ящиков отсинхронизировалась, а часть отвалилась по таймауту.
Параллельно pm.status показывает, что воркеры пула www регулярно упираются в pm.max_children: все процессы заняты, очередь растёт, обычные пользователи ждут. Пики совпадают с расписанием синхронизации почты.
Не делайте типичную ошибку: мы встречали крон с systemctl restart php-fpm каждые 15 минут, чтобы «расцепить» зависшие воркеры. Это убивает половину живых сессий и не лечит причину — через минуту воркеры снова блокируются на IMAP.
Где смотреть точно — в медленном логе PHP-FPM. Откройте /var/log/php-fpm/www-slow.log: если львиная доля стектрейсов упирается в check_mail.php и блокирующий fgets() на IMAP-сокете, диагноз готов. Вот характерный стек зависшего воркера:
script_filename = /home/bitrix/www/bitrix/tools/check_mail.php
[0x00007f0974813d80] fgets() /home/bitrix/www/bitrix/modules/mail/lib/imap.php:1814
[0x00007f0974813cb0] readLine() /home/bitrix/www/bitrix/modules/mail/lib/imap.php:1862
[0x00007f0974813c10] readResponse() /home/bitrix/www/bitrix/modules/mail/lib/imap.php:1715
[0x00007f0974813b70] exchange() /home/bitrix/www/bitrix/modules/mail/lib/imap.php:1696
[0x00007f0974813a90] executeCommand() /home/bitrix/www/bitrix/modules/mail/lib/imap.php:303
[0x00007f09748139e0] authenticate() /home/bitrix/www/bitrix/modules/mail/lib/imap.php:348

Почему IMAP блокирует PHP-FPM
Модуль «Почта» Битрикса синхронизируется с внешними ящиками по протоколу IMAP. Чтение из IMAP-сокета — блокирующая операция: PHP вызывает fgets() и ждёт ответа сервера. По RFC 3501 IMAP работает как диалоговый протокол поверх TCP, где клиент шлёт команду и читает ответ построчно. Пока ответ не пришёл, поток стоит.
Когда внешний сервер (Яндекс, Mail.ru, корпоративный Exchange) отвечает с задержкой, одна синхронизация ящика легко занимает десятки секунд. В коде модуля нет асинхронного ввода-вывода: воркер PHP-FPM, обслуживающий check_mail.php, всё это время полностью занят и недоступен для других запросов. Подробности работы модуля см. в документации Битрикса по модулю mail.
Дальше срабатывает арифметика. В стандартном bitrix-env весь PHP крутится в одном пуле www с типичным pm.max_children порядка 50. Если синхронизация запускается по нескольким ящикам сразу, несколько воркеров www намертво заняты ожиданием IMAP. Достаточно, чтобы число «зависших на почте» воркеров приблизилось к pm.max_children, и сайту физически нечем обслуживать живых посетителей. Внешне это выглядит как «Битрикс тормозит», хотя проблема локализована в одном скрипте.
Дополнительная сложность в том, что у внешних IMAP-серверов есть свои таймауты и троттлинг. Яндекс может задерживать ответ при подключении из «подозрительной» подсети, а корпоративный Exchange с включённым antispam-сканированием перед выдачей письма проверяет вложения. Каждая такая проверка добавляет секунды к ответу, тратя воркер PHP-FPM, который мог бы обслужить запрос к CRM.
PHP-FPM сам по себе не виноват: он честно отдаёт воркер запросу и ждёт, пока скрипт завершится. Архитектурно это правильно для синхронных HTTP-запросов, но плохо подходит для долгих внешних диалогов. Лечение — не переписывание модуля «Почта» (на это никто не пойдёт), а изоляция блокирующего скрипта в свой пул, где он не сможет перетянуть на себя ресурсы основного.

Решение: изолировать check_mail.php в отдельный пул
Лечится это не увеличением pm.max_children. Если поднять лимит на общем пуле, вы лишь дадите почте съесть больше памяти и в худшем случае получите OOM-killer вместо медленных страниц. Корректная стратегия — изолировать check_mail.php в собственный пул PHP-FPM с небольшим числом воркеров и жёстким таймаутом, а затем маршрутизировать в него ровно один URL. Всё остальное продолжает работать в основном пуле www и не замечает почтовых пробок.
Логика параметров такая. IMAP-операции долгие, параллелить их десятками воркеров бессмысленно: внешний почтовый сервер не станет отвечать быстрее, если мы будем долбить его в сто потоков. Поэтому пул держим небольшим — от трёх воркеров на портале со скромной почтой до десятка на нагруженном. Достаточно, чтобы синхронизация шла, и мало, чтобы потенциальное зависание не съело всю память. request_terminate_timeout гарантирует, что подвисший на сокете процесс будет убит, а не останется висеть навсегда. Параметры пула подробно описаны в документации PHP-FPM.
Отдельно стоит вынести лог. Если оставить медленные запросы почты в общем www-slow.log, диагностика реально медленных страниц превращается в раскопки: вы будете часами разгребать трейсы IMAP, прежде чем найдёте настоящую проблему в карточке сделки или генерации отчёта. Свой mail-slow.log для пула [mail] решает это раз и навсегда: один лог для почты, другой для всего остального.
Изоляция не заменяет настройку самой почты, но снимает главный риск: невозможность работать с CRM, пока почта тормозит. Дальше можно спокойно разбираться с конкретными ящиками: отключать те, что регулярно зависают, менять интервал синхронизации, заводить отдельный IMAP-прокси для проблемных Exchange. Всё это станет точечной оптимизацией, а не борьбой за выживание портала.
Архитектурно подход универсальный: любой блокирующий внешний интегратор в Битриксе (выгрузка в маркетплейс, опросы внешних API, экспорт в 1С) полезно держать в отдельном пуле. Почта — самый частый кандидат, потому что у неё фиксированная точка входа и предсказуемое расписание, но логика «один блокирующий скрипт = свой пул» работает и для остальных случаев.
Конфиг pool [mail] — CentOS / AlmaLinux (bitrix-env)
На AlmaLinux 9 с bitrix-env пул кладём отдельным файлом /etc/php-fpm.d/mail.conf. Так его проще отслеживать в git и легко откатить, не задевая конфиг основного www-пула. Внутри пишем минимум, но достаточный для изоляции.
Пользователь и группа: bitrix, как принято в bitrix-env. Apache крутится под тем же пользователем, и права на сокет совпадают. listen.mode = 0660 оставляет доступ только владельцу и группе, никакого «777 для верности». Это важно: на сопровождаемых порталах мы видели, как админ копировал чужой конфиг с 0666 и открывал FastCGI-сокет всему миру внутри хоста.
Стратегия менеджера процессов выбрана dynamic. На таком маленьком пуле разница со static минимальна, но dynamic корректнее переживает простой: воркеры подвисают на IMAP редко, а вне пиков пул вообще никому не нужен. pm.start_servers = 1 стартует один воркер на старте FPM, pm.max_spare_servers = 2 ограничивает запас.
pm.max_requests = 100 пересоздаёт воркер после ста запросов. Это страховка от утечек памяти, которыми славится длительная работа с IMAP и парсинг писем.
Пара таймаутов request_slowlog_timeout = 60s и request_terminate_timeout = 70s работает так: всё, что выполняется дольше минуты, попадает в mail-slow.log со стектрейсом, а всё, что висит больше 70 секунд, принудительно убивается. Разница в 10 секунд — намеренный буфер: сначала логируем причину, потом убиваем. Без этого буфера зависший процесс прибьётся раньше, чем slowlog успеет записать стек.
Файлы логов лежат отдельно от основного пула: mail-slow.log и mail-error.log создавать вручную не нужно, PHP-FPM сделает их сам при первом запуске, лишь бы каталог /var/log/php-fpm/ существовал и был доступен пользователю bitrix на запись. Вот конфиг целиком:
[mail]
user = bitrix
group = bitrix
listen = /run/php-fpm/mail.sock
listen.owner = bitrix
listen.group = bitrix
listen.mode = 0660
pm = dynamic
pm.max_children = 3
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 2
pm.max_requests = 100
request_slowlog_timeout = 60s
request_terminate_timeout = 70s
slowlog = /var/log/php-fpm/mail-slow.log
php_admin_value[error_log] = /var/log/php-fpm/mail-error.log
Конфиг pool [mail] — Debian
На Debian 12 пул лежит по стандартному для дистрибутива пути /etc/php/8.3/fpm/pool.d/mail.conf. Пользователь системного PHP-FPM здесь www-data, под ним же работает Apache. Конфиг чуть подробнее, чем на CentOS: добавлены clear_env, catch_workers_output, отдельный memory_limit под разбор писем с вложениями и более глубокий стек в slowlog.
request_slowlog_trace_depth = 40 даёт глубокий стек в mail-slow.log. Это удобно, когда нужно поймать, на какой именно операции IMAP залипает конкретный ящик: дефолтная глубина в 20 кадров часто обрезается в служебных классах Битрикса и не показывает реальную точку блокировки. Сорок кадров пробивают вглубь, к самому fgets().
memory_limit = 512M на воркер выглядит щедро, но письма с PDF-вложениями и встроенными изображениями быстро упираются в стандартные 256 МБ. Мы видели, как при разборе вложения с встроенной base64-картинкой воркер вырастал до нескольких сотен мегабайт и падал с fatal error по памяти. 512 МБ — запас, чтобы такие ящики не «выпадали» из синхронизации.
catch_workers_output = yes собирает то, что PHP пишет в STDOUT/STDERR во время выполнения скрипта (var_dump, варнинги, неперехваченные ошибки), без него такие сообщения уходят в никуда. Для модуля «Почта», который много общается с внешними сокетами, это критично.
Важно про env-переменные. clear_env = yes отрезает окружение shell, поэтому нужные коду Битрикса env[BITRIX_ENV_TYPE] и env[BITRIX_VA_VER] приходится прокидывать явно. Но не хардкодьте 9.0.7 вслепую — это версия конкретной сборки bitrix-env, и на вашем сервере она может отличаться. Возьмите актуальные значения из своего основного www-пула (grep env\[ /etc/php/8.3/fpm/pool.d/www.conf) или из версии окружения. На части сборок (например, AlmaLinux bitrix-env) этих переменных в пуле нет вовсе — тогда их можно не добавлять.
[mail]
user = www-data
group = www-data
listen = /run/php/mail.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 12
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 100
catch_workers_output = yes
decorate_workers_output = no
clear_env = yes
request_slowlog_timeout = 10s
request_slowlog_trace_depth = 40
slowlog = /var/log/php-fpm/mail-slow.log
request_terminate_timeout = 40s
php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/php-fpm/mail-error.log
php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 512M
php_admin_value[default_socket_timeout] = 20
php_value[session.save_handler] = files
php_value[session.save_path] = /var/lib/php/session
php_value[soap.wsdl_cache_dir] = /var/lib/php/wsdlcache
env[BITRIX_ENV_TYPE]=crm
env[BITRIX_VA_VER]=9.0.7
Маршрутизация check_mail.php в пул [mail]: Apache или nginx
Осталось направить в новый пул ровно одну точку входа — и где именно, зависит от сборки. В стандартном bitrix-env стек двухуровневый: nginx спереди проксирует в Apache (httpd), а PHP в пул отдаёт именно Apache через SetHandler. На минимальных и кастомных сборках Apache часто нет — nginx сам общается с PHP-FPM через fastcgi_pass. Проверьте свой стек одной командой systemctl is-active httpd apache2: если Apache активен — правило пишем в Apache; если жив только nginx — в nginx.
Вариант 1 — bitrix-env (nginx + Apache). Правило кладём в кастомный конфиг Apache: /etc/httpd/bx/custom/check_mail.conf на CentOS/AlmaLinux или /etc/apache2/bx/custom/check_mail.conf на Debian. Папку bx/custom/ bitrix-env специально оставляет под пользовательские правила — после обновления окружения конфиг не затрётся. <Location> перекрывает ровно один URL: не пишите <LocationMatch "^/bitrix/tools/"> или шире — в bitrix/tools/ лежат скрипты других модулей, и вы затолкнёте их в маленький почтовый пул. По нашему опыту такое расширение убивало webhook-и CRM (внешние сервисы попадали в пул [mail] и получали 503). Обёртка <IfModule proxy_fcgi_module> — защита: без модуля SetHandler был бы проигнорирован и Apache отдал бы исходник PHP как статику; с обёрткой правило просто не сработает, и скрипт уйдёт через общий пул. Сам синтаксис SetHandler proxy:unix:…|fcgi://localhost — штатный способ Apache отдать запрос в FastCGI-сокет, описан в документации mod_proxy_fcgi.
Вариант 2 — nginx без Apache. Тот же принцип, но реализуется в nginx: добавляем отдельный location = /bitrix/tools/check_mail.php с fastcgi_pass на сокет пула [mail]. Критично порядок: точный location = должен стоять выше общего location ~ \.php$, иначе общий перехватит запрос раньше. fastcgi_params включаем те же, что у основного PHP-location, меняется только сокет.
После применения (любой вариант) перезагрузите веб-сервер и curl-ом убедитесь, что URL отдаёт 200/302/403, а не 502 или листинг файлов; при 502 причину видно в mail-error.log — чаще всего неверный путь к сокету или несовпадение пользователя сокета и веб-сервера.
Правило для bitrix-env (Apache) — ниже, под ним эквивалент для nginx-only. На CentOS путь сокета — /run/php-fpm/mail.sock:
# Route /bitrix/tools/check_mail.php to dedicated mail php-fpm pool
# (isolates slow IMAP syncs from the main www pool)
SetHandler "proxy:unix:/run/php/mail.sock|fcgi://localhost"
# nginx без Apache: этот блок — ВЫШЕ общего "location ~ \.php$"
location = /bitrix/tools/check_mail.php {
fastcgi_pass unix:/run/php-fpm/mail.sock;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
Ключевые цифры — почему именно так
| Параметр | Значение | Зачем |
|---|---|---|
| pm.max_children | 3–12 | сколько синхронизаций тянуть параллельно; подбирается под объём почты — 3 при паре ящиков, 12 на портале с десятками активных |
| request_terminate_timeout | 40–70s | если IMAP-сокет завис, воркер не залипает навсегда; порог — по допустимой задержке синхронизации |
| request_slowlog_timeout | 10–60s | ниже terminate: успеть зафиксировать трейс зависшей сессии в slowlog до того, как воркер убьют |
| pm.max_requests | 100 | защита от утечек памяти PHP в коде синхронизации |
| memory_limit (Debian) | 512M | разбор писем с вложениями требует памяти |
| отдельный slowlog | mail-slow.log | основной www-slow.log остаётся чистым для диагностики реально медленных страниц |
Сколько воркеров. Главный принцип — выделить почте отдельный, ограниченный пул, а не раздувать общий. Точное число зависит от объёма: на портале с парой ящиков хватает трёх (наш CentOS-сервер), на портале с десятками активных ящиков мы поднимаем до 12 (Debian-сервер выше). Выше не уходим без необходимости: узкое место — внешний IMAP, и лишние воркеры просто параллельно ждут ответа сервера, расходуя память, а не ускоряют синхронизацию.
Почему жёсткий terminate_timeout. Нормальная IMAP-сессия укладывается в секунды. Если воркер висит десятки секунд — это уже симптом: медленный сервер, перегруженный канал, проблемный ящик. request_terminate_timeout (40–70s в зависимости от того, какую задержку вы готовы терпеть) убивает такой процесс, освобождая воркер под следующую итерацию. request_slowlog_timeout ставим ниже него, чтобы успеть записать трейс зависшей сессии в mail-slow.log до того, как процесс прибьют.
Почему 100 запросов на пересоздание. PHP-FPM на длительных IMAP-сессиях копит остатки в памяти после десятков циклов разбора писем. Сто запросов — порог, при котором свежий воркер ещё не мешает производительности, а утечка не успевает разрастись. Для особенно тяжёлых ящиков (Exchange с большими вложениями) уменьшайте до 50.
Не ставьте pm.max_children = 1, даже если кажется логичным «один скрипт — один воркер»: если единственный воркер залипнет, синхронизация встанет до срабатывания таймаута, и часть ящиков пропустит цикл. Минимум — три: при нём временное зависание одного ящика не блокирует всю очередь.
Итог
- Блокирующая природа IMAP-запросов в check_mail.php исчерпывает воркеры общего пула www, вызывая тормоза всего портала.
- Отдельный пул [mail] с небольшим числом воркеров (3–12 по объёму почты) и жёстким request_terminate_timeout изолирует долгие операции чтения почты от пользовательского трафика.
- Маршрутизация единственного URL /bitrix/tools/check_mail.php через Apache mod_proxy_fcgi направляет запросы в выделенный сокет, не затрагивая остальной сайт.
- Разделение логов slowlog позволяет чётко диагностировать реальные проблемы производительности CRM, отделяя их от штатных задержек синхронизации ящиков.
- Конфигурация легко адаптируется под CentOS/AlmaLinux (bitrix-env) и Debian с учётом различий в путях к сокетам и правах доступа пользователей.
Часто задаваемые вопросы
Ответы на часто задаваемые вопросы по теме статьи.
Константин Тютюнник — ведущий инженер IT For Prof. Специализируется на низкоуровневой оптимизации веб-серверов для высоконагруженных проектов на Битрикс24. Разрабатывает архитектурные решения по разделению сервисов PHP-FPM и настройке отказоустойчивых кластеров.
Если ваш коробочный Битрикс24 испытывает сбои в часы синхронизации или отдаёт ошибки 502, мы проведём аудит конфигурации сервера и настроим изоляцию процессов. Закажите сопровождение сервера Битрикс24 для стабильной работы CRM.




