Что в итоге сделаем и где искать причину ошибки 500 на Битрикс
Когда к нам приходят с задачей «сайт отдаёт 500, разберитесь», в девяти случаях из десяти мы находим причину не в самом Битриксе, а в фатальной ошибке PHP или в падении веб-сервера — например, из-за нехватки памяти воркеру PHP-FPM или синтаксической ошибки в только что отредактированном файле. Браузер в этот момент молчит: он получает от сервера сухой код 500 Internal Server Error и не показывает ни строки, ни файла, ни причины. Именно поэтому диагностика почти всегда начинается не с экрана, а с логов — и почти всегда заканчивается там же.
В проектах с активным импортом из 1С, с большим каталогом и с регулярными правками шаблонов 500-я всплывает внезапно: вчера всё работало, сегодня менеджер открывает карточку товара и видит белый экран. У магазинов на старом ядре добавляется свой слой — mysqli, который нужно включать отдельно для старого и нового ядра, и если его не хватает, падение тоже приходит как 500. По нашему опыту, задача «найти причину» решается за 10–20 минут, если знать, куда смотреть по порядку.
Порядок диагностики у нас такой:
- Лог веб-сервера — Apache (
/var/log/httpd/error_logв BitrixVM) или NGINX. Здесь видно, дожил ли запрос до PHP вообще: если в логе пусто, а браузер молчит — проблема на уровне FPM или самого сервера. bitrix/modules/error.log— путь по умолчанию в настройках Битрикса. Это первое место, где появляется строкаPHP Fatal errorс именем файла и номером строки.- Журнал в админке - Настройки → Производительность → Ошибки PHP. Удобно, когда нет доступа к серверу по SSH.
.settings.phpи.settings_extra.php— проверяем секциюexception_handlingи ключdebug. Второй файл может переопределять значения первого, и без его проверки можно долго смотреть не туда.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:
# последние 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. Разберём, что означает каждый ключ, и отдельно остановимся на подводных камнях, из-за которых «включил, а на экране пусто».
Ключ 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
// .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
// /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
// 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 и падением веб-сервера (мы разбирали это выше) и знаете, где в принципе лежат логи Битрикс. Задача секции — превратить это понимание в последовательность, которую можно выполнять сверху вниз, не перескакивая и не теряя нить. Проходите шаги по порядку: каждый следующий сужает область поиска.
- Посмотреть код ответа и заголовки через
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 может быть локальной — например, только на странице каталога. - Проверить лог веб-сервера (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 — смотрите его пул и лимиты. - Проверить
bitrix/modules/error.logи/bitrix/php_interface/error.log. Здесь уже пишет сам Битрикс — через свой обработчик. Пути не универсальны: в одних проектах лог лежит вbitrix/modules/error.log, в других — в/bitrix/php_interface/error.log, в третьих путь задан в настройках и указывает куда угодно. Проверьте оба файла и заодно посмотрите, куда указывает ключ лог-файла в.settings.php. Именно тут чаще всего видно стек вызовов, а не просто «Fatal error» без контекста. - Зайти в Настройки > Производительность > Ошибки PHP. Это встроенный журнал ошибок PHP в админке Битрикс. Если сайт ещё открывается в админке — заходите туда в первую очередь: журнал покажет те же фаталы, но уже с привязкой к странице и времени, без ручного поиска по серверным логам. Если админка тоже отдаёт 500 — этот шаг пропускаем и идём дальше, к настройкам.
- Сверить
.settings.phpи.settings_extra.phpна расхождения..settings_extra.phpможет переопределять значения из основного файла — и делает это молча. По документации он должен возвращать массив той же структуры, что и.settings.php, но API для него не существует, поэтому правки туда часто вносят руками и забывают. Если значения в двух файлах расходятся (например,debugили параметры подключения к БД), поведение сайта становится непредсказуемым. Сравните обе структуры построчно — расхождения объясняют значительную часть «загадочных» 500. - Проверить настройки
mysqliвdbconn.phpи.settings.php. Поддержкаmysqliвключается отдельно для старого ядра (в/bitrix/php_interface/dbconn.php) и для нового ядра (в.settings.php). Если обновляли PHP или ядро и забыли про одну из половин — сайт падает на подключении к БД сразу, отдавая 500. Заодно проверьте параметрDELAY_DB_CONNECT: если он включён, подключение к базе происходит при первом обращении через API, и ошибка тогда всплывает не на старте, а в момент первого запроса — это сбивает с толку при диагностике. - Воспроизвести на отладочном хосте с
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 и как закрыть тему раз и навсегда.
# Проверяем первые байты файла: 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 такую ситуацию обрабатывает штатно.
Переключение профиля и перегенерация файла — операция на пять минут:
- Открыть в админке Интернет-магазин → Настройки → Обмен с Яндекс.Маркет (путь может отличаться в зависимости от версии модуля).
- В списке профилей выгрузки найти активный и заменить
yandex-simpleнаyandex. - Сохранить настройки и запустить выгрузку заново — старый файл
yandex_*.phpудалится автоматически. - Проверить, что новый файл открывается в браузере без 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
// .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 и как связать его записи с фаталами.
# /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 объявлен первой строкой после