Назад в блог

Один Bitrix — два фронтенда: что реально ломается

У нас один PHP-бэкенд. Два Next.js-фронтенда. Два домена. Два бренда.

Это Bitrix headless с мультисайтовым API — не MultiSite из документации, а кастомная архитектура, собранная из необходимости. Она работает. Но с несколькими оговорками, которые мы узнали через боль.

Зачем один Bitrix на два фронтенда

Не потому что дёшево. Потому что данные общие.

Когда запускали второй сайт, 1C-синхронизация, справочники поставщиков и логика заказов уже жили в одном Bitrix. Поднимать второй инстанс означало бы держать два экземпляра этих данных в синхронизации. Это хуже.

Экономия на сервере — побочный эффект. Главный аргумент: одна точка правды для бизнес-данных.

Первая версия архитектуры выглядела просто: один PHP, два фронтенда на отдельных доменах, REST API без аутентификации на уровне домена. Через неделю после запуска первая проблема пришла в виде сессионного хаоса.

API-namespace как первое архитектурное решение

Первое, что мы сделали правильно: разделили namespace'ы ещё до запуска.

/api/ivanpin/services/
/api/services/wgp/

Это не просто структура папок. Это граница ответственности.

Каждый namespace имеет свой _common.php с маппингом, токеном и набором разрешённых операций. Когда фронтенд сайта A делает запрос — он попадает в свой namespace и не может случайно прочитать данные сайта B. Токен X-WGP-Token в заголовке — общий для бэкенда, но его проверяет namespace-конкретный обработчик.

Это сработало. Но только для изоляции доступа. Изоляция данных — отдельная история.

IBLOCK_ID как единица изоляции: что с этим не так

В Bitrix данные живут в инфоблоках. У каждого сайта — свои IBLOCK_ID.

Для ivanpin.com: 40 (Блог RU) и 41 (Блог EN). Для webgoodpeople.com — другие ID, другие правила.

Функция ivanpinApiResolveIblockId в _common.php делает маппинг при каждом запросе. Это работает.

Проблема в другом. Под нагрузкой, когда оба сайта одновременно делают запросы к одним и тем же сущностям Bitrix — процессы PHP начинают конкурировать за одни и те же блокировки кэша. Bitrix composite cache не знает про то, что у тебя два разных фронтенда. Он видит один сайт.

Два раза ловили ситуацию: один фронтенд инвалидирует кэш, второй получает холодный ответ, который генерируется заново под нагрузкой. TTFB на второй сайт вырастает на 200-400 мс — именно в те моменты, когда первый сайт активно пишет данные.

Решение простое, но неочевидное: отдельные секции кэша через CACHE_ID. Каждый namespace прописывает свой cache-salt. Теперь инвалидация одного сайта не задевает кэш второго.

Сессии на двух доменах: где утекало

Через неделю после запуска второго сайта получили странный баг: авторизованный пользователь на сайте A случайно видел данные пользователя B.

Диагноз занял день. Причина — PHPSESSID cookie без Domain-scope.

Bitrix по умолчанию выдаёт session cookie без явного домена. Если оба сайта висят на одном сервере и у них общий домен верхнего уровня — браузер может отправить один и тот же PHPSESSID на оба домена.

У нас домены были разными (ivanpin.com и webgoodpeople.com). Но оба сайта использовали одни PHP-сессии на сервере. Bitrix идентифицировал пользователя по PHPSESSID в Redis — и если два разных браузера с разных сайтов случайно имели одинаковый session ID (коллизия), получали данные друг друга.

Вероятность коллизии низкая, но не нулевая. Особенно при cold start Redis после перезагрузки.

Фикс: принудительный session_name() разный для каждого namespace. Для ivanpin: IVANPIN_SESSID. Для WGP: WGP_SESSID. Простое изменение, которое закрыло класс проблем навсегда.

CDN и общий бэкенд: cache-ключи вручную

CDN не знает про то, что у тебя два сайта. Он кэширует по URL.

Если /api/ivanpin/services/blog/list.php и /api/services/wgp/posts/list.php — это разные URL, CDN всё сделает правильно. И у нас так.

Проблема возникла с общими ресурсами. Изображения из /upload/ на бэкенде — это один URL-пространство для обоих сайтов. CDN кэшировал /upload/iblock/123/image.jpg один раз для всех доменов.

Когда один сайт обновлял картинку (новый файл с тем же именем) — второй сайт ещё несколько минут видел старую версию из CDN-кэша. Это не катастрофа, но раздражало.

Решение: в CDN-правилах прописали Vary: Origin. Теперь одинаковый URL с разных доменов-источников кэшируется отдельно. Cache hit rate немного упал — примерно с 89% до 84% — но корректность важнее.

Три триггера для разделения бэкендов

Мы не разделяем бэкенды. Но есть три ситуации, когда это стало бы правильным решением.

Первый: разные требования к uptime и деплою. Bitrix-обновление затрагивает оба сайта одновременно. Если для одного сайта это критично, а второй может потерпеть — лучше разделить.

Второй: один сайт начинает давить нагрузкой на второй. Один PHP pool на двоих. Если сайт B запустил маркетинговую кампанию и получил 10x трафик — сайт A это почувствует. Отдельные PHP-FPM pools помогают, но не решают полностью.

Третий: требования к безопасности данных расходятся. GDPR, персональные данные, разные юрисдикции — при отдельных бэкендах изоляция чище и проще аудировать.

Если ни одного из этих триггеров нет — один бэкенд оправдан. Сложность разделения выше, чем кажется: синхронизация общих справочников, двойная 1C-интеграция, два CI/CD пайплайна для PHP.


Один Bitrix на два headless-фронтенда — это не мультисайт-фича из коробки. Это ручная сборка: namespace, cache-salt, session_name. Большинство проблем решаются один раз и не возвращаются. Найти их в первый раз — дорого.

Внутренние ссылки: Bitrix REST API: пять сюрпризов · Headless — это про независимость деплоя · PHP зарабатывает деньги. Next.js их показывает.