Play
online · 30 мин
Туториалы 31 мин чтения 11.09.2026

Ошибка 500 на Битрикс: где искать причину в логах и настройках PHP

Сайт на Битриксе отдаёт 500, а браузер молчит и не показывает ни строки с причиной. В девяти случаях из десяти виновата не сама CMS, а фатальная ошибка PHP или упавший воркер. Разбираем, как найти виновника по логам и настройкам.

Что в итоге сделаем и где искать причину ошибки 500 на Битрикс

Когда к нам приходят с задачей «сайт отдаёт 500, разберитесь», в девяти случаях из десяти мы находим причину не в самом Битриксе, а в фатальной ошибке PHP или в падении веб-сервера — например, из-за нехватки памяти воркеру PHP-FPM или синтаксической ошибки в только что отредактированном файле. Браузер в этот момент молчит: он получает от сервера сухой код 500 Internal Server Error и не показывает ни строки, ни файла, ни причины. Именно поэтому диагностика почти всегда начинается не с экрана, а с логов — и почти всегда заканчивается там же.

В проектах с активным импортом из 1С, с большим каталогом и с регулярными правками шаблонов 500-я всплывает внезапно: вчера всё работало, сегодня менеджер открывает карточку товара и видит белый экран. У магазинов на старом ядре добавляется свой слой — mysqli, который нужно включать отдельно для старого и нового ядра, и если его не хватает, падение тоже приходит как 500. По нашему опыту, задача «найти причину» решается за 10–20 минут, если знать, куда смотреть по порядку.

Порядок диагностики у нас такой:

  1. Лог веб-сервера — Apache (/var/log/httpd/error_log в BitrixVM) или NGINX. Здесь видно, дожил ли запрос до PHP вообще: если в логе пусто, а браузер молчит — проблема на уровне FPM или самого сервера.
  2. bitrix/modules/error.log — путь по умолчанию в настройках Битрикса. Это первое место, где появляется строка PHP Fatal error с именем файла и номером строки.
  3. Журнал в админке - Настройки → Производительность → Ошибки PHP. Удобно, когда нет доступа к серверу по SSH.
  4. .settings.php и .settings_extra.php — проверяем секцию exception_handling и ключ debug. Второй файл может переопределять значения первого, и без его проверки можно долго смотреть не туда.
  5. ExceptionHandler и Diag\Debug — когда нужно поймать ошибку на конкретном участке кода, а не ждать, пока она сама всплывёт в логе.

Статья построена от симптома к причине. Сначала разберём, как отличить падение PHP от падения веб-сервера — это сужает круг поиска вдвое. Затем покажем, где именно лежат логи и как их читать, как включить вывод ошибок через .settings.php, как пользоваться ExceptionHandler и Diag\Debug, и дадим чеклист из 7 шагов. Отдельные разделы посвящены конкретным граблям, которые мы видим чаще всего: BOM в начале файла после правки, профиль yandex-simple в выгрузке каталога, настройки PHP для Битрикса и мониторинг медленных запросов. Каждый раздел самодостаточен. Если у вас уже есть гипотеза — открывайте нужный и пропускайте остальные.

Подробнее: BitrixVM показывает исходный код.

Подробнее: невосстановимая ошибка СУБД 1С.

Internal Server Error на Битрикс: как отличить падение PHP от падения веб-сервера

Типичный сценарий: сайт отдаёт 500, вы открываете браузер, видите пустую страницу или стандартную заглушку сервера — и дальше начинается гадание. Проблема в том, что HTTP 500 — это общий код, его возвращают все участники цепочки: nginx, Apache, PHP-FPM и само ядро Битрикса. Пока вы не поймёте, кто именно упал, вы будете чинить не то место.

В нашей практике больше половины времени в таких разборах уходит именно на локализацию — понять, что сломалось раньше, чем открыть лог. Если сразу посмотреть на код ответа вместе с заголовками и телом, источник сужается до одной из четырёх зон: веб-сервер (nginx/Apache), менеджер PHP (PHP-FPM), интерпретатор PHP или приложение (ядро Битрикс + модули). Дальше уже каждый участник пишет свою ошибку в свой лог — и вот там появляется конкретика.

Эта секция — про то, как за минуту понять, кто упал. Дальше в статье мы разберём, где лежат логи и как их читать, как включить вывод через .settings.php и exception_handling, и пройдём чеклист из семи шагов. Но без правильной локализации вы просто откроете не тот файл.

Симптом Кто виноват Куда смотреть Что искать
Пустая страница, 500 без тела PHP (fatal error) error_log PHP, лог Битрикс PHP Fatal error, Parse error
500 от nginx, «Bad Gateway» PHP-FPM не отвечает nginx/error.log, лог FPM upstream timed out, connect() failed
500 от Apache, страница с ошибкой сервера Apache / mod_php httpd/error_log AH-коды, .htaccess
500 + страница «Битрикс: ошибка» Ядро Битрикс лог exception_handling Стек исключения D7

Разберём, как читать эту матрицу на практике. Первое, что стоит сделать — открыть ответ через curl -I или вкладку Network в браузере и посмотреть заголовок Server. Если там nginx и тело пустое — почти наверняка упал PHP, а не веб-сервер: nginx просто транслирует ответ от FPM. Если nginx сам вернул страницу с «502 Bad Gateway» или «504 Gateway Time-out» — проблема в FPM: процесс не стартовал, упал воркер или упёрся в таймаут.

Второй маркер — тело ответа. Пустое тело при 500 — это классика PHP fatal error: интерпретатор умер до того, как что-то вывел. Тело с HTML-страницей «Internal Server Error» и подписью сервера — это уже сам веб-сервер (Apache или nginx) перехватил ошибку. А если вы видите страницу с логотипом Битрикса и текстом об ошибке — сработал ExceptionHandler ядра, и стек исключения уже лежит в логе, настроенном через exception_handling.

Третий признак — код ответа в связке с редиректом или отсутствием оного. Когда падает PHP-FPM на уровне подключения, nginx обычно отдаёт 502, а не 500. Когда падает сам PHP — 500. Когда падает ядро Битрикс — тоже 500, но с записью в лог. Разница именно в том, кто первым сформировал ответ.

Не отключайте display_errors на проде ради быстрой диагностики. Включение вывода ошибок на рабочем сайте раскрывает посетителям пути к файлам, версии модулей и структуру БД — это прямая утечка. Используйте лог-файл и, если нужно повторить ошибку вживую, отдельный хост или staging-копию. На проде display_errors = Off, а диагностика — только через лог.

Логи ошибок Битрикс: где лежат и как их читать

Самый частый вопрос на этом этапе диагностики звучит так: «а где вообще лежит лог ошибок Битрикс?» Отвечаем сразу: единого «дефолтного» пути не существует. Это не баг платформы и не недоработка документации — путь к логу задаётся в настройках, а не определяется автоматически. Из-за этого вторичные источники называют разные «стандартные» адреса: bitrix/modules/error.log, /bitrix/php_interface/error.log, __bx_log.log в корне. Все они встречаются в реальных проектах, но ни один не является нормой. Это просто значение, которое кто-то когда-то прописал в конфиге.

У нас в практике первым делом мы не гадаем по названиям файлов, а идём в конфигурацию и смотрим, что там реально указано. Именно оттуда растёт путь к логу, и именно там видно, включён ли он вообще. Разберём все места, где лог может оказаться, — по частоте встречаемости и по способу настройки.

Путь к логу Когда используется Как настроить Источник
bitrix/modules/error.log Классическая «дефолтная» настройка в старых проектах Параметр log в секции exception_handling файла .settings.php mvmolkov.ru, maxyss.ru
/bitrix/php_interface/error.log Проекты, где конфиг живёт в старом месте Тот же параметр, но с другим путём в значении osinpro.ru
__bx_log.log в корне Когда лог вынесен из bitrix/ для удобства чтения Произвольный путь в параметре log вторичные источники
/var/log/httpd/error_log BitrixVM, лог веб-сервера Apache Настраивается в конфиге Apache, не в Битриксе rushstudio.by
Настройки > Производительность > Ошибки PHP Просмотр через админку, без доступа к ФС Включается в интерфейсе администратора dev.1c-bitrix.ru

Проверить, что реально прописано в вашем проекте, можно в двух файлах: .settings.php в корне и .settings_extra.php — второй может переопределять значения первого, и про него часто забывают. Если путь в конфиге не указан или указывает в никуда — лог не пишется, и вы будете смотреть в пустой файл, искренне не понимая почему.

Теперь про чтение. Формат у лога Битрикса простой: timestamp + тип ошибки + сообщение + файл и строка. Читать его нужно не подряд, а целенаправленно, иначе утонете в тысячах warning'ов и не дойдёте до причины 500.

Порядок разбора у нас такой. Сначала ищем по строке PHP Fatal error — именно фаталы роняют страницу в 500. Warning'и и notice'ы интересны, но причиной падения почти никогда не являются. Затем смотрим timestamp: он должен совпадать с моментом, когда сайт начал отдавать ошибку. Если фатал записан в 14:32, а клиент жалуется на падения с утра — это, скорее всего, разные истории.

Дальше сопоставляем запись с действием. Ошибка пришла в момент, когда менеджер сохранял товар в админке? Или в 03:00, когда по расписанию отработал агент? Или в момент выгрузки каталога в Яндекс? Контекст — половина диагноза. Смотрим файл и строку из лога, открываем код, читаем, что там происходит. Часто уже на этом шаге видно: обращение к несуществующему методу, битый namespace, отсутствующий модуль. Если в логе несколько фаталов подряд с одним timestamp — берите первый: остальные могут быть следствием.

Быстрый способ посмотреть последние фаталы без открытия файла целиком — через tail и grep:

SHELL
# последние 50 строк лога Битрикса
tail -n 50 /home/bitrix/www/bitrix/modules/error.log

# только фаталы, последние 20 записей
grep "PHP Fatal error" /home/bitrix/www/bitrix/modules/error.log | tail -n 20

# фаталы за конкретный промежуток (например, с 14:00 до 15:00)
grep "PHP Fatal error" /home/bitrix/www/bitrix/modules/error.log | grep -E "1[45]:[0-9]{2}"

# лог веб-сервера — на случай, если ошибка до ядра Битрикса
tail -n 100 /var/log/httpd/error_log

Лог Битрикса может быть пустым, даже если сайт падает. Так бывает, когда ошибка произошла до инициализации ядра: битый autoload, падение на этапе подключения .settings.php, ошибка в .htaccess или в самом PHP-FPM. В этом случае смотрите лог веб-сервера (/var/log/httpd/error_log для BitrixVM или соответствующий путь вашего стека) и лог PHP-FPM — там будет запись. Если и там пусто — значит, ошибка на уровне инфраструктуры: не хватает памяти, отвалилась БД, упал процесс.

Как включить вывод ошибок через .settings.php и exception_handling

Многие инструкции в интернете до сих пор отправляют править /bitrix/php_interface/dbconn.php и /bitrix/php_interface/init.php — и это работает, но только для старого ядра. В текущих версиях Битрикс файл настроек переехал в корень сайта и называется .settings.php. Именно его читает новое ядро (D7) при инициализации: и подключение к БД, и работа с сессиями, и обработка ошибок через секцию exception_handling. Когда сайт отдаёт 500 и мы уже посмотрели логи (как разбирали выше), следующий шаг — убедиться, что debug вообще включён. Часто выясняется, что коллега когда-то выключил его «чтобы не светить пути», а обратно не включил. Или что настройка включена в .settings.php, но перекрыта другим файлом — об этом ниже.

Ниже — минимальная рабочая конфигурация exception_handling. Разберём, что означает каждый ключ, и отдельно остановимся на подводных камнях, из-за которых «включил, а на экране пусто».

PHP

Ключ debug — главный: он определяет, показывать ли ошибку на экране посетителю. Поставьте true только на отладочном хосте и не забудьте вернуть false перед выкладкой на прод. Ключ log управляет записью в файл: в settings.file указывается путь относительно корня, в level — минимальный уровень (в примере выше пишем только ошибки, но можно понизить до LEVEL_DEBUG, если нужно ловить вообще всё).

Дальше — два массива с типами ошибок. handled_errors_types отвечает за то, какие ошибки PHP вообще попадут в обработчик (и, соответственно, в лог). exception_errors_types — за то, какие из них выбросят исключение и покажутся на экране при debug = true. Разница важна: можно писать в лог всё, но не пугать посетителя варнингами. В примере мы исключили E_NOTICE и E_DEPRECATED — они чаще всего шумят на легаси-коде и к 500-й ошибке отношения не имеют. Если диагностируете именно фатал — оставьте только E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR, чтобы не отвлекаться.

Последний ключ — readonly. Если поставить true, настройки нельзя будет изменить через API в рантайме, только правкой файла. Для прода это разумная защита: никакой скрипт случайно не переключит debug. Для отладочного хоста удобнее false, чтобы можно было экспериментировать из консоли или временного скрипта.

Когда debug=true, а на экране пусто: проверьте .settings_extra.php

Распространённый сценарий: правите .settings.php, ставите debug = true, обновляете страницу — и ничего. Ошибка по-прежнему прячется за стандартной заглушкой 500. Почему так: рядом с основным файлом может лежать .settings_extra.php, который переопределяет значения из .settings.php. Официально он предназначен для динамических настроек (например, зависимых от окружения), но по факту туда часто пишут то, что не хотят коммитить в основной конфиг. Проверьте оба файла и убедитесь, что debug не выставлен в false именно в _extra. Второй частый случай — кэш opcache: PHP мог закешировать старую версию .settings.php. Перезапустите PHP-FPM или дождитесь инвалидации.

Чем DEBUG в новом ядре отличается от $DBDebug в старом

В старом ядре режим отладки включался переменной $DBDebug = true в dbconn.php — и влиял в основном на работу с базой: показывал SQL-запросы, ошибки подключения и т.п. В новом ядре параметр DEBUG.settings.php он задаётся через секцию exception_handling.debug) отвечает за вывод ошибок на экран в целом: и исключений D7, и фаталов PHP, и предупреждений. Это не одно и то же. Если вы переносите старый проект и по привычке ищете $DBDebug — его может уже не быть, а поведение будет определяться .settings.php. И наоборот: включённый debug в новом ядре не заменяет $DBDebug для старого кода, который всё ещё работает через старое ядро. В проектах с миксом старого и нового API приходится держать оба флага.

PHP
<?php
// .settings_extra.php — переопределяет значения из .settings.php
return [
    'exception_handling' => [
        'value' => [
            'debug' => true, // временно, для отладки на этом хосте
            'log' => [
                'settings' => [
                    'file' => 'local/logs/error-' . date('Y-m-d') . '.log',
                ],
            ],
        ],
        // readonly не указываем — наследуется из .settings.php
    ],
];

Как использовать ExceptionHandler и Diag\Debug для отладки 500

Распространённая картина при разборе 500-й: в error.log пусто или там только общая запись вида «PHP Fatal error», а сама ошибка воспроизводится стабильно — при заходе в конкретный раздел, при открытии карточки товара, при вызове какого-то экшена. Штатный обработчик ошибок не всегда пишет то, что нужно: фатал мог случиться до того, как ядро установило свой хендлер, либо ошибка ловится и гасится где-то выше по стеку, либо сообщение усечено до одной строки без контекста. В такой ситуации мы не гадаем, а подключаем инструменты D7 напрямую — Bitrix\Main\Diag\ExceptionHandler и Bitrix\Main\Diag\Debug. Первый управляет режимом отладки и перехватом исключений на раннем этапе загрузки, второй позволяет вывести значение переменной в лог, не ломая отдачу страницы. Это не замена нормальной настройке exception_handling в .settings.php (о ней мы говорили выше), а точечный инструмент на время диагностики.

В проектах с активным импортом, AJAX-компонентами и агентами эти два класса экономят часы: вместо слепого поиска по коду вы получаете точку падения и состояние переменных в момент ошибки. Ниже — как их включать и что логировать перед падением.

Как включить режим отладки через Bitrix\Main\Diag\ExceptionHandler

Класс ExceptionHandler из пространства Bitrix\Main\Diag содержит методы для установки режима отладки и настройки перехвата исключений. Его удобно использовать, когда нужно временно поднять уровень детализации, не трогая основной файл настроек и не рискуя забыть выключить debug на продакшене. Мы обычно регистрируем хендлер в самом начале init.php — до того, как отработают модули и компоненты. Так ловятся исключения, которые штатный обработчик пропустил бы. Включать это на боевом сайте можно только на короткое окно диагностики и с ограничением доступа по IP. Иначе наружу утекут пути к файлам и фрагменты кода.

PHP
<?php
// /bitrix/php_interface/init.php — только на время диагностики
use Bitrix\Main\Application;

// получаем экземпляр обработчика исключений
$handler = Application::getInstance()->getExceptionHandler();

// включаем режим отладки и логирование через методы экземпляра
$handler->setDebug(true);
$handler->setLogging(true);

// регистрируем кастомный обработчик для записи фатальных ошибок в файл
$handler->setHandler(function (\Throwable $e) {
        $logDir = $_SERVER['DOCUMENT_ROOT'] . '/local/logs/';
        if (!is_dir($logDir)) {
            mkdir($logDir, 0755, true);
        }
        file_put_contents(
            $logDir . 'debug_' . date('Y-m-d') . '.log',
            date('H:i:s') . ' ' . $e->getMessage()
            . ' in ' . $e->getFile() . ':' . $e->getLine() . PHP_EOL
            . $e->getTraceAsString() . PHP_EOL,
            FILE_APPEND
        );
});

Замените путь к логу на удобный вам — например, /bitrix/php_interface/error.log, если так принято в проекте. Если ошибка воспроизводится только при определённом запросе, добавьте в замыкание проверку $_SERVER['REQUEST_URI'] и пишите лог лишь для нужного URL — так файл не разрастётся.

Как посмотреть переменную в D7: Diag\Debug::dump и его отличие от var_dump

Классический var_dump() с die() на боевом сайте — плохая идея: он выводит дамп прямо в поток ответа, ломает вёрстку, а в AJAX-обработчиках провоцирует ошибки парсинга JSON. Метод Diag\Debug::writeToFile($var) из D7 решает это иначе: он записывает значение переменной в лог-файл и не прерывает выполнение скрипта. Страница отдаётся пользователю как обычно, а вы изучаете данные в логе. Если же требуется именно вывод на экран в читаемом виде, используется Debug::dump() — это штатный аналог var_dump, который удобно применять для быстрой проверки $arResult в шаблонах компонентов.

PHP
<?php
// local/templates/.default/components/bitrix/catalog.element/.default/template.php
use Bitrix\Main\Diag\Debug;

// пишем в файл /bitrix/php_interface/error.log (или куда указано в настройках)
Debug::dump($arResult, 'arResult-товар');

// можно вывести отдельное значение с меткой
Debug::dump($arParams['IBLOCK_ID'], 'ID инфоблока');

// и посмотреть конкретный элемент массива
Debug::dump($arResult['ITEM']['PRICES'], 'цены');

Второй аргумент — произвольная метка, по которой вы потом найдёте нужный дамп в общем логе. Если лог пишется в файл по умолчанию, посмотрите путь в настройках exception_handling — на разных проектах мы видели и bitrix/modules/error.log, и /bitrix/php_interface/error.log.

Что логировать перед падением, чтобы не гоняться за ошибкой по десять раз:

  • Входные параметры запроса — $_REQUEST, $arParams, тело AJAX-запроса: часто падение вызвано неожиданным типом или отсутствующим полем.
  • ID сущности, с которой работаете — элемента, раздела, заказа: по нему потом воспроизведёте ошибку точечно.
  • Стек вызовов — $e->getTraceAsString() в момент исключения, чтобы увидеть, какой именно компонент или агент уронил страницу.
  • Последний выполненный SQL — через $connection->getTracker()->getQueries() или включённый трекер запросов: частая причина 500 — запрос к несуществующей таблице или битый where.

500 ошибка на сайте Битрикс: чеклист из 7 шагов диагностики

Когда сайт отдаёт 500 и в браузере пусто, самое бесполезное — открывать файлы и что-то править наугад. У нас в студии на такие вызовы есть отработанный порядок: сначала снимаем факты (код ответа, логи, настройки), и только потом трогаем код. Так экономится больше времени, чем любой «быстрый фикс». 500 — это почти всегда следствие, а причина обычно на один-два уровня глубже: в логе PHP, в расхождении настроек или в подключении к БД.

Ниже — тот же чеклист, по которому мы идём сами. Он рассчитан на то, что вы уже понимаете разницу между падением PHP и падением веб-сервера (мы разбирали это выше) и знаете, где в принципе лежат логи Битрикс. Задача секции — превратить это понимание в последовательность, которую можно выполнять сверху вниз, не перескакивая и не теряя нить. Проходите шаги по порядку: каждый следующий сужает область поиска.

  1. Посмотреть код ответа и заголовки через curl -I. Прежде чем что-то трогать, зафиксируйте, что именно отдаёт сервер: curl -I https://site.ru/. Смотрим на первую строку (HTTP/1.1 500 Internal Server Error) и на заголовки Server, X-Powered-By. Если код 500 приходит мгновенно — падает PHP на раннем этапе (подключение к БД, автоподготовка). Если ответ висит и обрывается по таймауту — это уже другой сценарий, ближе к max_execution_time и тяжёлым запросам. Заодно проверьте конкретный URL, а не только главную: 500 может быть локальной — например, только на странице каталога.
  2. Проверить лог веб-сервера (Apache/PHP-FPM) на последние фаталы. Это первый источник, где видно сам факт падения. У Apache лог обычно лежит в /var/log/httpd/error_log (в BitrixVM — именно там), у NGINX — в /var/log/nginx/error.log, у PHP-FPM — свой error.log и отдельный slow log. Смотрим хвост: tail -n 100 /var/log/httpd/error_log. Ищите строки PHP Fatal error, PHP Parse error, Allowed memory size exhausted, Maximum execution time exceeded. Если PHP-FPM отдаёт 502/504 попеременно с 500 — смотрите его пул и лимиты.
  3. Проверить bitrix/modules/error.log и /bitrix/php_interface/error.log. Здесь уже пишет сам Битрикс — через свой обработчик. Пути не универсальны: в одних проектах лог лежит в bitrix/modules/error.log, в других — в /bitrix/php_interface/error.log, в третьих путь задан в настройках и указывает куда угодно. Проверьте оба файла и заодно посмотрите, куда указывает ключ лог-файла в .settings.php. Именно тут чаще всего видно стек вызовов, а не просто «Fatal error» без контекста.
  4. Зайти в Настройки > Производительность > Ошибки PHP. Это встроенный журнал ошибок PHP в админке Битрикс. Если сайт ещё открывается в админке — заходите туда в первую очередь: журнал покажет те же фаталы, но уже с привязкой к странице и времени, без ручного поиска по серверным логам. Если админка тоже отдаёт 500 — этот шаг пропускаем и идём дальше, к настройкам.
  5. Сверить .settings.php и .settings_extra.php на расхождения. .settings_extra.php может переопределять значения из основного файла — и делает это молча. По документации он должен возвращать массив той же структуры, что и .settings.php, но API для него не существует, поэтому правки туда часто вносят руками и забывают. Если значения в двух файлах расходятся (например, debug или параметры подключения к БД), поведение сайта становится непредсказуемым. Сравните обе структуры построчно — расхождения объясняют значительную часть «загадочных» 500.
  6. Проверить настройки mysqli в dbconn.php и .settings.php. Поддержка mysqli включается отдельно для старого ядра (в /bitrix/php_interface/dbconn.php) и для нового ядра (в .settings.php). Если обновляли PHP или ядро и забыли про одну из половин — сайт падает на подключении к БД сразу, отдавая 500. Заодно проверьте параметр DELAY_DB_CONNECT: если он включён, подключение к базе происходит при первом обращении через API, и ошибка тогда всплывает не на старте, а в момент первого запроса — это сбивает с толку при диагностике.
  7. Воспроизвести на отладочном хосте с debug=true и Diag\Debug. Только после того, как факты собраны, поднимаем копию на dev-хосте, включаем вывод ошибок через exception_handling -> value -> debug = true в .settings.php и расставляем точки через Bitrix\Main\Diag\Debug::dump($var). На проде display_errors держим выключенным — иначе пути к файлам увидит любой посетитель. На отладочном хосте спокойно воспроизводим сценарий, ловим конкретное место падения и уже там правим код.

Почему падает сайт после правки файла: BOM и Namespace declaration

На ru.stackoverflow разбирали характерный случай: сайт внезапно начал отдавать 500, а в логе появилась запись PHP Fatal error: Namespace declaration statement has to be the very first statement in the script в файле /bitrix/modules/main/lib/db/mysqlcommonconnection.php. Файл ядра, никто его осознанно не правил, а сайт лежит. Причина бытовая: файл открывали в редакторе, редактор сохранил его в UTF-8 с BOM, и перед открывающим <?php появились три невидимых байта. PHP 8.2 (как и любая версия начиная с 5.3) требует, чтобы namespace был самой первой инструкцией в скрипте. BOM сдвигает его на три байта вправо, и парсер валится с фаталом ещё до того, как успевает что-то вывести. Браузер показывает пустую страницу, сервер отдаёт 500, в логе видно ровно эту строку. У магазинов, где кто-то из админов правит ядро через файловый менеджер хостинга или FTP-клиент с «умным» сохранением, такие поломки мы видим регулярно. Диагностика занимает минуту, если знать, куда смотреть: в отличие от ошибок конфигурации из предыдущих секций, здесь проблема не в настройках, а в байтах самого файла. Дальше покажем, как проверить файл на BOM и как закрыть тему раз и навсегда.

SHELL
# Проверяем первые байты файла: BOM в UTF-8 — это EF BB BF
hexdump -C /home/bitrix/www/bitrix/modules/main/lib/db/mysqlcommonconnection.php | head -1

# Ожидаемый вывод для чистого файла (начинается с <?php = 3C 3F 70 68 70):
# 00000000  3c 3f 70 68 70 0a 0a 6e  61 6d 65 73 70 61 63 65  |<?php..namespace|

# Если видите в начале EF BB BF — это BOM, его нужно убрать:
# 00000000  ef bb bf 3c 3f 70 68 70  0a 0a 6e 61 6d 65 73 70  |...<?php..namesp|

# Альтернатива через file — покажет кодировку с пометкой BOM
file /home/bitrix/www/bitrix/modules/main/lib/db/mysqlcommonconnection.php
# UTF-8 Unicode (with BOM) text, with CRLF line terminators

# Массовая проверка всех PHP-файлов модуля на BOM
find /home/bitrix/www/bitrix/modules/main -name "*.php" -exec sh -c \
'head -c3 "$1" | grep -q $"\xef\xbb\xbf" && echo "BOM: $1"' _ {} \;

BOM (Byte Order Mark) — служебный маркер EF BB BF, который редакторы добавляют в начало файла, чтобы явно указать, что текст в UTF-8. Исторически он был нужен для различения big-endian и little-endian в UTF-16, но в UTF-8 практической пользы не несёт, зато ломает всё, что ожидает увидеть первым байтом что-то осмысленное. Блокнот Windows, старые версии Notepad++, некоторые веб-редакторы хостингов сохраняют файлы именно так: не по злому умыслу, а потому что это их дефолт. В PHP до 5.3 BOM тоже был проблемой (ломалась отправка заголовков), но фатала не вызывал. Начиная с 5.3, когда появились пространства имён, правило стало жёстким: namespace должен быть первым statement'ом в файле, без пробелов, переводов строк, комментариев и уж тем более байтов перед ним. BOM физически стоит перед <?php, PHP видит его как вывод (три байта уходят в output buffer), а затем встречает namespace и падает с фаталом. В PHP 8.2 сообщение об ошибке стало точнее, чем в 7.x: теперь оно прямо указывает на проблему первого statement'а, а не на «unexpected T_NAMESPACE», как раньше. Это упрощает диагностику. Если файлов много и вы не знаете, какой именно побился, ищите по стеку из лога: фатал всегда называет конкретный путь. Уберите BOM через sed -i '1s/^\xEF\xBB\xBF//' file.php или пересохраните файл в редакторе с явным выбором «UTF-8 without BOM», и сайт поднимется без перезапуска PHP-FPM, потому что opcache подхватит изменение по mtime.

Грабля: увидев фатал в bitrix/modules/main/lib/db/mysqlcommonconnection.php, хочется открыть этот файл и что-то в нём поправить. Не делайте этого. Файлы в bitrix/modules/ это ядро, и первое же обновление модуля затрёт вашу правку. Но самое опасное другое: если BOM появился в одном файле, он почти наверняка есть и в других, например в вашем собственном обработчике в local/php_interface/init.php или в кастомном компоненте, куда вы заходили тем же редактором. Убирайте BOM не только из файла из стека, а прогоняйте find по всему проекту: local/, bitrix/php_interface/, шаблон сайта. Иначе через день поймаете ту же 500-ю на другом файле и снова будете искать причину с нуля.

500 при выгрузке каталога в Яндекс: почему профиль yandex-simple ломает экспорт

На ru.stackoverflow разбирали характерный случай: при открытии экспортного файла вида yandex_7645267234.php, созданного через профиль yandex-simple, сервер отдаёт 500 Internal Server Error. Файл генерируется, лежит на диске, но при обращении к нему — пустая страница и запись в логе. Это второй публичный кейс, который мы регулярно вспоминаем в разговорах про 500-ю на Битриксе. Он хорошо показывает, что причина не всегда в коде или конфигурации сервера, а в настройках конкретного модуля. Диагностика здесь идёт не через .settings.php и не через PHP-логи, а через понимание того, чем один профиль выгрузки отличается от другого. Если вы уже прошли чеклист из семи шагов и не нашли причину — самое время посмотреть на профиль обмена с Яндексом. В проектах с активной интеграцией с Яндекс.Маркетом мы всегда держим этот пункт в голове: сгенерированный файл может быть валидным XML, но точка входа в него — сломанной.

Ниже разберём, почему yandex-simple вообще существует, кому он подходит и в какой момент превращается в источник фатальной ошибки.

Профиль yandex-simple — это упрощённый шаблон выгрузки, который идёт в комплекте с модулем обмена и рассчитан на небольшие каталоги и минимальный набор полей. Он генерирует файл экспорта с базовой структурой: название, цена, ссылка, картинка, описание. Полноценный профиль yandex умеет больше: работает с торговыми предложениями, поддерживает произвольные поля, корректно обрабатывает большие объёмы и разбивает выгрузку на части. Разница проявляется именно на масштабе.

Когда SKU становится много, yandex-simple пытается собрать весь каталог в один проход и в один файл. На каталогах от нескольких тысяч позиций это упирается в лимит памяти PHP, в таймаут скрипта или в ограничение на размер генерируемого файла. Скрипт падает с фаталом, а поскольку точка входа — тот самый yandex_*.php, браузер получает 500-ю. В логе при этом может быть что угодно: Allowed memory size exhausted, Maximum execution time exceeded или пустота, если вывод ошибок выключен. Мы уже разбирали выше, как читать такие логи — здесь важен сам факт: падение привязано к профилю, а не к серверу.

Второй момент — архитектура самого файла. yandex-simple в ряде версий модуля генерирует точку входа, которая не рассчитана на повторный запуск и не сбрасывает состояние корректно. При повторном обращении к файлу (например, робот Яндекса зашёл дважды) накопленные данные конфликтуют, и скрипт снова падает. Полноценный профиль yandex такую ситуацию обрабатывает штатно.

Переключение профиля и перегенерация файла — операция на пять минут:

  1. Открыть в админке Интернет-магазин → Настройки → Обмен с Яндекс.Маркет (путь может отличаться в зависимости от версии модуля).
  2. В списке профилей выгрузки найти активный и заменить yandex-simple на yandex.
  3. Сохранить настройки и запустить выгрузку заново — старый файл yandex_*.php удалится автоматически.
  4. Проверить, что новый файл открывается в браузере без 500-й и отдаёт валидный XML.

Настройки PHP для Битрикс: mysqli, сессии и opcache

Бывает так: логи чистые, файлы никто не трогал, а сайт стабильно отдаёт 500 или «белый экран» на конкретном разделе. Причина обычно не в коде, а в конфигурации PHP — расширениях, сессиях или opcache. У нас на проектах это регулярно всплывает после переезда на новый сервер или после обновления ядра: код тот же, а окружение другое. Разбираем обязательный минимум, который стоит проверить до того, как лезть в дебри приложения. Все настройки ниже — из системных требований Битрикс, а не из «народных рецептов».

Параметр Значение Где задавать Зачем
mysqli включено dbconn.php и .settings.php Подключение к MySQL через новое расширение
session.cookie_httponly On php.ini / пул PHP-FPM Cookie сессии недоступны из JS
session.cookie_samesite Lax php.ini / пул PHP-FPM Защита от CSRF и потерянных сессий
opcache.max_accelerated_files 100000 php.ini Хватает слотов для файлов ядра
opcache.revalidate_freq 0 php.ini Проверка изменений при каждом запросе

Почему mysqli включается отдельно для старого и нового ядра

Ключевой момент, на котором спотыкаются чаще всего: поддержку mysqli нужно включать в двух местах независимо друг от друга. Старое ядро читает настройки подключения из /bitrix/php_interface/dbconn.php, новое (D7) — из .settings.php в корне сайта. Это два разных механизма, и синхронизируются они не сами.

Если включить только в одном файле, часть системы продолжит работать на старом драйвере mysql. На современных сборках PHP это расширение либо отсутствует, либо помечено как устаревшее. И тогда вылезает ровно та 500-я, которую мы диагностировали в предыдущих разделах: в логе Call to undefined function mysql_connect() или похожее. Причём ошибка всплывёт не сразу, а на первом обращении к базе через конкретный слой — например, при работе старого модуля или агента.

Поэтому правило простое: проверяем оба файла и убеждаемся, что mysqli включён и там, и там. В dbconn.php это константа подключения, в .settings.php — секция connections. Если проект обновлялся с давних версий, старый файл могли просто забыть — и он тихо тянет за собой устаревший драйвер.

PHP
<?php
// .settings.php — секция подключения к БД
return [
    'connections' => [
        'value' => [
            'default' => [
                'className' => '\\Bitrix\\Main\\DB\\MysqliConnection',
                'host'      => 'localhost',
                'database'  => 'bitrix',
                'login'     => 'bitrix_user',
                'password'  => 'your_password',
                'options'   => 2, // замените на ваши параметры подключения
            ],
        ],
        'readonly' => true,
    ],
    'cache' => [
        'value' => [
            'type' => ['class' => '\\Bitrix\\Main\\Data\\CacheEngineFiles'],
        ],
    ],
    // Отложенное подключение: соединение с БД открывается
    // при первом запросе через API, а не при загрузке страницы
    'delay_db_connect' => [
        'value' => true,
        'readonly' => true,
    ],
];

Отложенное подключение к базе даёт заметный выигрыш на страницах без обращений к БД — например, на кэшированных лендингах. Соединение открывается автоматически при первом запросе через API-функции, что на высоконагруженных проектах снижает число одновременных коннектов к MySQL.

Не пропускайте настройки сессий. Если session.cookie_httponly выключен, а session.cookie_samesite не установлен в Lax, браузеры начинают вести себя непредсказуемо: часть запросов приходит без cookie сессии, пользователей выкидывает из авторизованной зоны, а в логах растёт поток ошибок авторизации. На первый взгляд это баг приложения, но лечится двумя строками в php.ini или в конфиге пула PHP-FPM. Проверьте оба параметра на боевом окружении — они входят в системные требования Битрикс, а не являются опциональным «усилением безопасности».

Мониторинг медленных запросов PHP-FPM и MySQL: чтобы 500 не повторялась

Бывает так: 500-ю поймали, причину нашли, файл поправили — а через неделю она возвращается, уже на другом разделе. По нашему опыту это почти всегда означает одно: сайт упирается в таймаут на медленном запросе, а не в битый код. PHP-FPM отдаёт фатал по истечении request_terminate_timeout, веб-сервер показывает 500, а в error.log при этом либо пусто, либо одна сухая строка без контекста. Понять, какой именно запрос не уложился в лимит, из такого лога невозможно.

Поэтому после разбора конкретного инцидента мы всегда настраиваем slow log — отдельный журнал медленных запросов PHP-FPM. Он пишет не факт падения, а стек вызовов в момент, когда скрипт работает дольше заданного порога. Дальше по этому стеку видно, что тормозит: тяжёлый CIBlockElement::GetList без индексов, getNext по большому HL-блоку или запрос в MySQL, который висит на блокировке. Комбинируется это с логированием MySQL — вместе они дают полную картину «кто кого ждал». Ниже — как включить slow log и как связать его записи с фаталами.

SHELL
# /etc/php/8.2/fpm/pool.d/www.conf (или php-fpm.d/www.conf)
; Порог в секундах: запрос дольше  попадёт в slow log
request_slowlog_timeout = 5s

; Куда писать стек медленных запросов
slowlog = /var/log/php-fpm/slow.log

; Уровень детализации стека (по умолчанию 1  достаточно)
slowlog_timeout = 5s

; Лог фаталов PHP-FPM  рядом, чтобы сопоставлять по времени
php_admin_value[error_log] = /var/log/php-fpm/error.log

# После правки — перезагрузить пул
# systemctl reload php8.2-fpm

# MySQL: включаем slow query log (my.cnf, секция [mysqld])
# slow_query_log = 1
# slow_query_log_file = /var/log/mysql/slow.log
# long_query_time = 2
# log_queries_not_using_indexes = 1

Ключевое здесь — request_slowlog_timeout: он не роняет запрос, а только фиксирует стек, когда время выполнения превышено. Порог подбирается под проект: для каталога на десятки тысяч SKU мы обычно ставим 5 секунд, для простого корпоративного сайта хватает 2–3 секунд. Значение request_terminate_timeout (реальный обрыв) должно быть заметно выше — иначе slow log не успеет ничего записать до фатала. Это первое, что стоит поправить под себя.

Дальше — сопоставление. В slow.log каждая запись содержит дату, PID пула и полный стек вызовов в момент срабатывания порога. В error.log PHP-FPM пишет фаталы вида execution timed out с тем же PID и близким временем. Совпадение по PID + времени — и вы точно знаете, какой скрипт не уложился. Обычно виновник оказывается в одном из трёх мест: неоптимизированный компонент, тяжёлый агент по расписанию или запрос без индекса. Последний ловится уже в MySQL slow log — ищите там SELECT по b_iblock_element_property или b_catalog_product с большим rows_examined. Мы в таких случаях сначала смотрим стек PHP, потом идём в MySQL за тем же временным окном — так виновник находится за пару минут, а не за час гадания.

Чтобы точно зафиксировать время выполнения запроса на каждом уровне, используйте штатные переменные логов:

  • $request_time — в логах NGINX, полное время обработки запроса в секундах;
  • %D — в логах Apache, время выполнения в микросекундах;
  • %{mili}d — в логах PHP-FPM, время обработки запроса в миллисекундах.

Самые частые ошибки, которые мы видим при диагностике 500 на Битрикс

За годы разбора «сайт отдаёт 500» у нас сложилась устойчивая статистика: примерно в половине случаев причина не в самом коде, а в том, как именно диагностировали. Администратор или разработчик лезет в ядро, правит файлы на проде, включает отладку на живом трафике — и вместо решения получает второй, третий инцидент. Мы собрали грабли, на которые наступают чаще всего: от правки bitrix/modules вместо /local до включения debug=true прямо на боевом хосте.

Ниже таблица «грабля → причина → как чинить», а после неё два случая, которые мы разбираем отдельно: повторяются они с завидной регулярностью и стоят дороже всего.

Грабля Причина Как чинить
Правка файлов в bitrix/modules Обновление ядра затирает фикс Перенести в /local или обработчик
BOM в начале PHP-файла Редактор сохранил в UTF-8 with BOM Пересохранить «UTF-8 без BOM»
debug=true на проде Утечка путей и данных посетителям Отладка только на отдельном хосте
Правка .settings.php без бэкапа Синтаксическая ошибка в массиве Копия файла до правки, проверка lint
Ожидание «дефолтного» лога Путь задаётся в конфиге, не универсален Смотреть exception_handling и .settings_extra.php

Если по итогам диагностики вы поймали себя хотя бы на двух пунктах из таблицы — это нормально. Через эти грабли проходит почти каждый, кто администрирует Битрикс без выделенного DevOps.

Важно не то, что ошиблись, а то, что следующий разбор пройдёт быстрее: логи на месте, отладка на изолированном хосте, фиксы — в /local.

Подробнее об услуге: поддержка и сопровождение сайтов на Битрикс.

Частые вопросы

Можно ли включить exception_handling в .settings.php только для админки, чтобы клиенты не видели трейс?

Да, в .settings.php есть ключ 'debug' => true и 'exception_handling' => ['value' => ['debug' => true]], но проще ограничить вывод через проверку $USER->IsAdmin() в собственном обработчике set_exception_handler. Либо держите debug включённым только на dev-копии, а на проде читайте trace из лога, а не из браузера.

Что делать, если в error.log пусто, а 500 всё равно есть?

Смотрите не только error_log PHP, но и лог веб-сервера: /var/log/nginx/error.log или /var/log/apache2/error.log — часто там видно 'Premature end of script headers' или 'upstream timed out'. Также проверьте php-fpm.log и уровень error_reporting: при E_ALL & ~E_ERROR ошибки просто не пишутся.

Чем отличается ExceptionHandler из D7 от set_exception_handler в старом ядре?

Класс \Bitrix\Main\Diag\ExceptionHandler инициализирует обработку ошибок через метод initializeErrorHandlers(), используя конфигурацию из секции exception_handling. Это позволяет ядру Битрикс логировать исключения (например, в /bitrix/modules/error.log) согласно заданным типам ошибок. Прямое использование PHP-функции set_exception_handler() не учитывает настройки системы, из-за чего логи могут записываться некорректно.

А если у меня 500 появляется только при выгрузке в Яндекс.Маркет — где искать?

Смотрите профиль экспорта в /bitrix/admin/iblock_export_edit.php: у yandex-simple часто не хватает полей в инфоблоке (например, CML2_ARTICLE или CML2_BAR_CODE), и падает на этапе формирования XML. Включите debug в .settings.php и откройте лог — там будет конкретный инфоблок и строка, где вылетает исключение.

Можно ли ловить 500 через Diag\Debug::writeToFile без правки .settings.php?

Да, \Bitrix\Main\Diag\Debug::writeToFile($var, 'label', '/local/logs/debug.log') пишет в указанный файл независимо от exception_handling. Но для перехвата фатальных ошибок PHP всё равно нужен обработчик — Debug ловит только то, что вы явно передали.

Что делать, если после правки файла появился BOM и сайт упал в 500?

BOM (EF BB BF) в начале PHP-файла ломает отправку заголовков и вызывает 'headers already sent'. Уберите BOM в редакторе (в Notepad++ — «Кодировка → Преобразовать в UTF-8 без BOM») и проверьте, что namespace объявлен первой строкой после

#Bitrix #PHP #Apache #Nginx
автор · Backend / SRE Engineer
Артем Колячек

Backend-разработчик и SRE в студии Paradigma. Занимается тем, что у других проектов обычно обнаруживается за неделю до запуска: переездом Bitrix-проектов между серверами, починкой кешей после миграций, выстраиванием pipeline для контейнеров, мониторингом под нагрузкой.

До Paradigma — 7 лет в backend (PHP + Postgres) и в operations (Linux, Docker, Coolify, restic-бэкапы). Любит когда логи разговаривают полным синтаксисом ошибки, а не «что-то пошло не так».

На блоге пишет ровно про те ситуации с которыми сам разбирался руками: какой Bitrix-апдейт сломал кеш и как откатить, почему Docker-сеть не находит контейнер после рекрейта, что делать когда asyncpg ругается на event loop.