Битрикс24: отдельный PHP-FPM пул для IMAP — почему модуль «Почта» тормозит весь сервер — обложка

Битрикс24: отдельный PHP-FPM пул для IMAP — почему модуль «Почта» тормозит весь сервер

Битрикс синхронизация почты часто становится скрытой причиной деградации производительности коробочного портала. Днём система работает стабильно, но в часы обновления ящиков административная панель перестаёт отвечать, а пользователи сталкиваются с долгими загрузками страниц и ошибками 502. При этом мониторинг показывает низкую нагрузку на процессор и базу данных, что сбивает с толку при первичной диагностике.

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

Битрикс24: отдельный PHP-FPM пул для IMAP — почему модуль «Почта» тормозит весь сервер

Изоляция скрипта 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-запросов, но плохо подходит для долгих внешних диалогов. Лечение — не переписывание модуля «Почта» (на это никто не пойдёт), а изоляция блокирующего скрипта в свой пул, где он не сможет перетянуть на себя ресурсы основного.

Почему IMAP блокирует PHP-FPM

Решение: изолировать 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)
<Location "/bitrix/tools/check_mail.php">
    <IfModule proxy_fcgi_module>
        SetHandler "proxy:unix:/run/php/mail.sock|fcgi://localhost"
    </IfModule>
</Location>
				
			
				
					# 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_children3–12сколько синхронизаций тянуть параллельно; подбирается под объём почты — 3 при паре ящиков, 12 на портале с десятками активных
request_terminate_timeout40–70sесли IMAP-сокет завис, воркер не залипает навсегда; порог — по допустимой задержке синхронизации
request_slowlog_timeout10–60sниже terminate: успеть зафиксировать трейс зависшей сессии в slowlog до того, как воркер убьют
pm.max_requests100защита от утечек памяти PHP в коде синхронизации
memory_limit (Debian)512Mразбор писем с вложениями требует памяти
отдельный slowlogmail-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 является блокирующей операцией: PHP-воркер ожидает ответа от почтового сервера, который может занимать десятки секунд. В стандартной конфигурации bitrix-env все процессы обслуживаются общим пулом www. Когда несколько ящиков синхронизируются одновременно, они занимают все доступные воркеры pm.max_children, из-за чего обычные запросы пользователей встают в очередь или получают ошибку 502.
Он создаёт изолированную среду для выполнения скрипта синхронизации почты. Мы выделяем небольшое число воркеров (от 3 при скромной почте до 12 на нагруженном портале) с собственным socket-файлом и жёстким ограничением времени выполнения (request_terminate_timeout). Это гарантирует, что даже если IMAP-сервер зависнет, процессы не заблокируют основной сайт, а будут принудительно завершены, освободив ресурсы только внутри своего малого пула.
Увеличение числа воркеров в общем пуле лишь откладывает проблему и требует больше оперативной памяти. Почта продолжит занимать процессы, необходимые для отображения страниц CRM. Изоляция через отдельный пул [mail] эффективнее: она физически разделяет потоки данных, позволяя основному сайту работать стабильно независимо от скорости ответа внешних почтовых серверов.
Основные различия касаются путей к файлам и системных пользователей. В CentOS/AlmaLinux (bitrix-env) используется пользователь bitrix и сокет в /run/php-fpm/, тогда как в Debian применяется пользователь www-data и сокет в /run/php/. Число воркеров подбирается под объём почты (у нас 3 на CentOS-портале и 12 на нагруженном Debian); также в Debian-конфиге мы явно указываем параметры сессий и кэша WSDL и увеличиваем memory_limit до 512M для обработки крупных вложений.

Константин Тютюнник — ведущий инженер IT For Prof. Специализируется на низкоуровневой оптимизации веб-серверов для высоконагруженных проектов на Битрикс24. Разрабатывает архитектурные решения по разделению сервисов PHP-FPM и настройке отказоустойчивых кластеров.

Если ваш коробочный Битрикс24 испытывает сбои в часы синхронизации или отдаёт ошибки 502, мы проведём аудит конфигурации сервера и настроим изоляцию процессов. Закажите сопровождение сервера Битрикс24 для стабильной работы CRM.

Поделиться: