Overview of Technologies for Bitrix Website Development
Rus
Eng
Обзор технологий для разработки сайтов на Битрикс
I solve complex technical challenges for Bitrix and 1C—ranging from auditing existing code to website development and configuring complex integrations—with a guarantee of results.
Recommended

Website hosting

If you are choosing hosting for your project, I recommend Timeweb. Modern infrastructure, a wide range of plans from starter options to high-load VPS, and responsive tech support that actually helps instead of sending template replies. I host some of my own projects there and recommend it to my clients.

Go to site Affiliate link. The price remains the same for you.

All English-language materials have been translated fully automatically using the Google service

Modern Bitrix Development: An Overview of Technologies, Infrastructure, and Practices

Current as of 2026. This material was verified for 1C-Bitrix: Site Management (current releases), PHP 8.2/8.3, MySQL 8.0 / Percona Server 8.0. Before applying anything, check the vendor's official documentation: PHP and module support depends on the core release.

Why This Is an Overview, Not an Engineering Guide

Initially, this material was conceived as an engineering and methodological breakdown of modern Bitrix development: with complete examples, error analysis, and step-by-step instructions. But the further I went, the more obvious it became that the topic does not fit into a single article. When the number of subtopics exceeded twenty, the draft exceeded fifteen thousand lines, and each topic was at best left unexplored — it became clear that such a format does not work. Properly covering each topic means separate articles, not one.

So the concept changed. What you have before you is an overview of technologies for developing websites on Bitrix. What to use, when it is needed, and how to use it. Each topic is concise, to the point, with examples and a breakdown of pros and cons, so it is clear whether a given technology is worth using in a specific project. Whether to go deeper is up to you.

The material is aimed at teams that build websites on 1C-Bitrix — from small studios to enterprise development. Everything described here is used when deploying Bitrix projects in production mode and when evolving existing Bitrix online stores.

Important: The code examples in this article are educational and simplified. They demonstrate an approach, not a ready-made production solution. Before using them on a live server, adapt them to your project and test them on staging.

Part 1. Infrastructure and DevOps

Why BitrixVM Is Usually Only for Getting Started

BitrixVM is a ready-made virtual machine image with a preconfigured stack: Nginx, PHP, MySQL, memcached, push&pull. For quickly deploying a test site, training, or a pilot project, it is convenient: install the image, run the setup script, and get a working Bitrix in half an hour.

Pros: fast start, everything is preconfigured, fewer chances of making a mistake during deployment.

Cons: it drags along a pool of packages whose versions lag behind current ones; platform updates can break the Nginx or PHP configuration; you cannot flexibly manage PHP versions for different projects on the same server.

For production, a clean stack on Ubuntu or Debian with Nginx, PHP-FPM 8.2 or 8.3, Percona Server or MySQL 8.0 is usually preferable. The PHP version is chosen for the specific project: Bitrix runs stably on 8.2; 8.3 is suitable for new sites without legacy modules, but compatibility should be verified against the vendor's matrix.

Nginx Config for Bitrix: SEO-Friendly URLs, Composite Cache, Static Files, Timeouts

The Nginx configuration for Bitrix development has several mandatory points. First — try_files with urlrewrite.php, otherwise SEO-friendly URLs will not work. Second — serving the composite cache directly from Nginx: if the ready html file exists, serve it; otherwise pass the request to PHP-FPM. Third — correct handling of static files. Fourth — increased fastcgi_read_timeout for heavy operations. Fifth — the client_max_body_size directive, which defaults to 1 MB in Nginx and cuts off file uploads through the admin panel and forms. For an online store with image and document uploads, it should be raised to at least 64 MB, preferably 128 MB, synchronized with upload_max_filesize and post_max_size in PHP.

The Bitrix composite cache is stored as ready-made index.html files at /bitrix/html_pages/<host><uri>/index.html. This is exactly the path that should be checked first:

server {
    listen 80;
    server_name domain.ru;
    root /var/www/domain.ru;

    index index.php;

    client_max_body_size 128M;
    client_body_buffer_size 4M;

    location / {
        try_files /bitrix/html_pages/$host$uri/index.html $uri $uri/ /bitrix/urlrewrite.php?$args;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        fastcgi_read_timeout 300;
        fastcgi_send_timeout 300;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
        expires 30d;
        access_log off;
    }

    # Protect the composite cache from direct access
    location ~ ^/bitrix/html_pages/ {
        internal;
    }
}

Without location ~ ^/bitrix/html_pages/ { internal; }, composite index.html files can be opened via direct links — this is a content leak and a potential indexing problem. Without the correct path $host$uri/index.html, the composite cache is not served at all, and the whole point of enabling it is lost: the load on PHP-FPM remains the same.

Pros of the composite cache: reduces the load on PHP-FPM several times over, pages are served in milliseconds. Cons: it does not work on dynamic pages with forms, shopping carts, or personal data — there the composite cache has to be disabled. For a Bitrix online store with traffic of 50,000+ hosts per day, the composite cache is an important element, but it should be applied selectively.

Docker and Docker Compose

Deploying a Bitrix project manually on every server is labor-intensive and prone to inconsistencies between environments. Docker and Docker Compose provide reproducibility: the same stack on a local machine, on staging, and on production. There are ready-made images for Bitrix, but it is better to build your own based on php:8.2-fpm with the required extensions.

version: '3.8'

services:
  app:
    build: ./docker/php
    volumes:
      - ./:/var/www/html
    depends_on:
      - db
      - redis

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./:/var/www/html
      - ./docker/nginx/bitrix.conf:/etc/nginx/conf.d/default.conf

  db:
    image: percona:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
    volumes:
      - db_data:/var/lib/mysql

  redis:
    image: redis:7-alpine

volumes:
  db_data:

Pros: the same environment for the whole team, fast onboarding of new developers, easy to spin up a dozen environments for parallel work. Cons: on production, Docker adds performance overhead (usually minor, but depends on network configuration, file system, and load), requires discipline with volumes and secrets, and adds a layer of abstraction that the whole team needs to understand. For small projects on a single server, classic deployment without containers may be simpler.

Multi-Environment: dev / stage / prod

Three environments are the minimum for commercial Bitrix development. Local development happens on dev, testing before release on stage, and the live server is prod. Each environment has its own settings: database paths, API keys, logging level. It is convenient to put all of this in .env and load it through third-party libraries like symfony/dotenv. Note: .env is not a built-in Bitrix mechanism but an external practice that must be explicitly wired into the project.

The rule is simple: .env never goes into the repository. The repository contains .env.example with a list of variables; the actual values are configured on each server separately.

Git Architecture and .gitignore

The most common mistake when building Bitrix sites is dragging the entire /bitrix folder into Git. The platform core is not your code, it is updated by the vendor, and versioning it creates conflicts on every update. The modern approach: all custom code lives in /local, while the core and user files are not versioned.

Only project-related content goes into the repository: /local, configs, composer.json, and composer.lock. The /upload directory contains user files (images, documents uploaded through the admin panel) and should not be versioned — only /upload/.htaccess or an empty directory structure can be stored in Git if required for deployment.

/bitrix/*
!/bitrix/.htaccess

/upload/*
!/upload/.htaccess

/local/cache/
/local/logs/

/vendor/
*.log
*.sql
*.bak
*.zip
.env
.env.*
!.env.example
.idea/

If a project historically has custom templates or components inside /bitrix, this exception must be explicitly documented and, where possible, moved to /local. Keeping /bitrix/templates and /bitrix/components in Git is an outdated practice that leads to conflicts during core updates.

Pros: clean history, no conflicts with the core, fast clone. Cons: you need to set it up carefully once, otherwise you risk accidentally committing the core or, conversely, losing local/php_interface during an update.

Branching Strategy (Simplified GitFlow)

For Bitrix development, a simplified GitFlow works well. Two long-lived branches: master — stable, from which production is deployed, and develop — the development branch, from which staging is deployed. Each task is handled in a separate feature/* branch, which is merged into develop via a merge request. Urgent fixes go into hotfix/*, which are merged into both master and develop.

Pros: predictability, no one pushes directly to the stable branch, easy to roll back an unsuccessful release. Cons: for a project with a single developer, GitFlow is excessive ceremony; trunk-based development with short-lived branches and frequent releases is simpler.

Composer: Dependency Management

Composer in Bitrix is not about installing the core, but about third-party libraries. Logging via Monolog, working with REST API via Guzzle, dependency injection via php-di.

{
    "require": {
        "monolog/monolog": "^3.0",
        "guzzlehttp/guzzle": "^7.0",
        "php-di/php-di": "^7.0"
    },
    "autoload": {
        "psr-4": {
            "ProjectScope\\": "local/lib/"
        },
        "classmap": [
            "local/components/project/*/src"
        ]
    },
    "config": {
        "optimize-autoloader": true,
        "sort-packages": true
    }
}

Pros: industry standard, autoload instead of manual require, easy update installation. Cons: on some shared hostings, composer is unavailable; old PHP versions are not supported by modern libraries. For projects on current PHP 8.2+, there are no problems.

CI/CD: build → test → deploy Pipeline

When deploying via rsync, it is critical to exclude /bitrix and /upload — otherwise the --delete option will wipe out the platform core and user files on the server, and the project will stop working within a second. Cache directories, logs, and local settings are also excluded:

stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - composer install --no-dev --optimize-autoloader
    - npm ci && npm run build

test:
  stage: test
  script:
    - vendor/bin/phpstan analyse
    - vendor/bin/php-cs-fixer fix --dry-run --diff
    - vendor/bin/phpunit

deploy:
  stage: deploy
  script:
    - rsync -a --delete
        --exclude=/bitrix
        --exclude=/upload
        --exclude=/local/cache
        --exclude=/local/logs
        --exclude=/bitrix/html_pages
        --exclude=/.env
        ./ user@server:/var/www/domain.ru/
    - ssh user@server "cd /var/www/domain.ru && composer install --no-dev"

Pros: automation, reproducible deployments, rollback via git revert. Cons: initial setup takes time; for small projects with a single developer, the complexity does not always pay off. But if you release at least once a week, CI/CD is justified. The rsync exclusions are not a detail but a mandatory part of the pipeline.

Backup Strategy

Backups in Bitrix development are not a "we'll do it later" matter. Two things are mandatory: a database dump and a copy of the files. The dump is made via mysqldump with the --single-transaction option to avoid locking tables.

mysqldump --single-transaction --routines --triggers \
  --user=${DB_USER} --password=${DB_PASSWORD} \
  ${DB_NAME} | gzip > /backups/db_$(date +%Y%m%d_%H%M%S).sql.gz

Pros: minimal insurance against disaster. Cons: mysqldump on a million-item catalog can take hours and consume a lot of I/O. For large projects, it is better to use Percona XtraBackup — it performs hot backups without locking tables.

Part 2. Database

When an Information Block Is Overkill

Information blocks are a powerful tool, but each property is a record in a separate table, and each filter is a JOIN. For a blog, news, or product catalog, information blocks are justified. For a directory of cities or a list of statuses, they are not. The sign: the data is flat, there is no hierarchy, there are few fields, and filtering is simple.

Highload Block or Custom ORM Table (DataManager)

A Highload block is a table generator based on D7 ORM, created from the admin panel, supports UF properties, and provides a ready-made interface. A custom ORM table is a class inheriting from Bitrix\Main\Entity\DataManager, giving full control over the schema, but you write the interface and data handling yourself.

Pros of Highload blocks: quick to create, ready-made UI, no need to write admin panel code. Cons: only simple indexes, no foreign keys, no migrations. Pros of DataManager: full control over indexes and relations, migrations, typing. Cons: everything must be written by hand, including the interface.

The criterion: if you need composite indexes and relations — DataManager; if the data is flat and you need a ready-made interface — Highload block. For a catalog with a million products, a Highload block often does not fit precisely because composite indexes cannot be added.

Migrations: MigrationRunner, up()/down(), Version Table

Bitrix has no built-in migration mechanism, so you need your own runner. It consists of three parts: a version table in the database, migration classes with up() and down() methods, and a runner that applies only new migrations.

Important: the example below is educational. In production, you need transactions per migration, locking against parallel runs, checksums, a rollback attempt limit, and logging. Do not copy this code into a live project without adaptation.

The version table is created once during initialization:

CREATE TABLE project_migrations (
    ID INT AUTO_INCREMENT PRIMARY KEY,
    VERSION VARCHAR(255) NOT NULL UNIQUE,
    APPLIED_AT DATETIME NOT NULL
);

A migration class is a regular PHP class with two methods. No inheritance from the core, so that the migration does not depend on the Bitrix version:

&lt;?php

class Migration20260101AddPhoneColumn
{
    public function up(): void
    {
        $connection = \Bitrix\Main\Application::getConnection();
        $connection->queryExecute("
            ALTER TABLE project_orders
            ADD COLUMN PHONE VARCHAR(20) DEFAULT NULL
        ");
    }

    public function down(): void
    {
        $connection = \Bitrix\Main\Application::getConnection();
        $connection->queryExecute("
            ALTER TABLE project_orders
            DROP COLUMN PHONE
        ");
    }
}

The runner scans the migrations directory, compares class names with records in the version table, and applies only those that are missing. Rollback is called manually — down() is needed when a migration has broken production and you need to return to the previous schema:

&lt;?php

class MigrationRunner
{
    /** @var string Directory with migration classes */
    private string $migrationsDir;

    public function __construct(string $migrationsDir)
    {
        $this->migrationsDir = rtrim($migrationsDir, '/');
    }

    /**
     * Applies all new migrations from the directory.
     *
     * @return array List of applied versions
     */
    public function run(): array
    {
        $applied = $this->getAppliedVersions();
        $migrations = $this->discoverMigrations();
        $appliedNow = [];

        foreach ($migrations as $version => $class) {
            if (in_array($version, $applied, true)) {
                continue;
            }

            /** @var object $instance */
            $instance = new $class();

            if (!method_exists($instance, 'up')) {
                throw new \RuntimeException("Migration {$class} has no up() method");
            }

            $instance->up();
            $this->markApplied($version);
            $appliedNow[] = $version;
        }

        return $appliedNow;
    }

    /**
     * Rolls back the specified version.
     *
     * @param string $version Migration version
     * @return void
     */
    public function rollback(string $version): void
    {
        $migrations = $this->discoverMigrations();

        if (!isset($migrations[$version])) {
            throw new \RuntimeException("Migration {$version} not found");
        }

        $class = $migrations[$version];
        $instance = new $class();

        if (!method_exists($instance, 'down')) {
            throw new \RuntimeException("Migration {$class} has no down() method");
        }

        $instance->down();
        $this->markRolledBack($version);
    }

    private function getAppliedVersions(): array
    {
        $connection = \Bitrix\Main\Application::getConnection();
        $result = $connection->query('SELECT VERSION FROM project_migrations');

        $versions = [];
        while ($row = $result->fetch()) {
            $versions[] = $row['VERSION'];
        }

        return $versions;
    }

    private function discoverMigrations(): array
    {
        $files = glob($this->migrationsDir . '/Migration*.php');
        $migrations = [];

        foreach ($files as $file) {
            $class = basename($file, '.php');

            if (!class_exists($class)) {
                require_once $file;
            }

            $version = preg_replace('/^Migration(\d+).*$/', '$1', $class);
            $migrations[$version] = $class;
        }

        ksort($migrations);

        return $migrations;
    }

    private function markApplied(string $version): void
    {
        $connection = \Bitrix\Main\Application::getConnection();
        $helper = $connection->getSqlHelper();

        $connection->queryExecute(sprintf(
            "INSERT INTO project_migrations (VERSION, APPLIED_AT) VALUES ('%s', NOW())",
            $helper->forSql($version)
        ));
    }

    private function markRolledBack(string $version): void
    {
        $connection = \Bitrix\Main\Application::getConnection();
        $helper = $connection->getSqlHelper();

        $connection->queryExecute(sprintf(
            "DELETE FROM project_migrations WHERE VERSION = '%s'",
            $helper->forSql($version)
        ));
    }
}

Launch from a CLI script during deployment:

&lt;?php

require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';

$runner = new MigrationRunner(__DIR__ . '/migrations');
$applied = $runner->run();

echo 'Applied migrations: ' . implode(', ', $applied) . PHP_EOL;

Pros: the same schema across all environments, easy rollback, visible change history. Cons: the Bitrix core does not use migrations, and if your entities overlap with core tables, you may encounter unexpected conflicts. Migrations should be applied only to your own tables.

MySQL Indexing for Catalogs of 100,000+ Products

The standard Bitrix facet index stops coping with 6–8 active filters. Manual indexes are needed. For queries with correlated EXISTS on IBLOCK_ELEMENT_ID, the column order is: (IBLOCK_ELEMENT_ID, IBLOCK_PROPERTY_ID, VALUE_ENUM).

ALTER TABLE b_iblock_element_prop_m14
ADD INDEX idx_prop_m14_element_prop_enum (IBLOCK_ELEMENT_ID, IBLOCK_PROPERTY_ID, VALUE_ENUM),
ALGORITHM=INPLACE, LOCK=NONE;

Pros: speeds up specific heavy queries several times over, full control. Cons: every extra column in an index slows down inserts and increases database size; indexes should not be created "just in case" — only for a specific query after EXPLAIN. On production, ALTER TABLE is performed with ALGORITHM=INPLACE, LOCK=NONE.

Queues: Queue Table and Third-Party Brokers

Heavy operations should not be performed at the moment of a user request. There are two levels of complexity, each solving its own class of tasks.

Queue table. Tasks are placed in a custom project_queue table; a worker reads a batch once a minute via SELECT ... FOR UPDATE SKIP LOCKED, processes it, and deletes it. Implemented in pure SQL and PHP, with no external dependencies. Works well up to several thousand tasks per hour.

Important: the example below is simplified. In production, you need recovery of "stuck" tasks by timeout, an attempt limit, a dead letter queue, idempotent handlers, and HANDLER validation against a whitelist.

CREATE TABLE project_queue (
    ID INT AUTO_INCREMENT PRIMARY KEY,
    HANDLER VARCHAR(255) NOT NULL,
    PAYLOAD TEXT NOT NULL,
    STATUS ENUM('new', 'in_progress', 'done', 'failed') DEFAULT 'new',
    ATTEMPTS INT DEFAULT 0,
    CREATED_AT DATETIME NOT NULL,
    UPDATED_AT DATETIME DEFAULT NULL,
    INDEX idx_status_created (STATUS, CREATED_AT)
);
// Enqueue a task
$connection = \Bitrix\Main\Application::getConnection();
$helper = $connection->getSqlHelper();

$connection->queryExecute(sprintf(
    "INSERT INTO project_queue (HANDLER, PAYLOAD, CREATED_AT) VALUES ('%s', '%s', NOW())",
    $helper->forSql($handler),
    $helper->forSql(json_encode($payload))
));
// Worker, run via cron once a minute
$connection = \Bitrix\Main\Application::getConnection();
$connection->queryExecute("START TRANSACTION");

$result = $connection->query("
    SELECT ID, HANDLER, PAYLOAD
    FROM project_queue
    WHERE STATUS = 'new'
    ORDER BY ID
    LIMIT 100
    FOR UPDATE SKIP LOCKED
");

$tasks = [];
while ($row = $result->fetch()) {
    $tasks[] = $row;
}

if ($tasks) {
    $ids = array_column($tasks, 'ID');
    $connection->queryExecute(
        "UPDATE project_queue SET STATUS = 'in_progress' WHERE ID IN (" . implode(',', $ids) . ")"
    );
}

$connection->queryExecute("COMMIT");

foreach ($tasks as $task) {
    try {
        $handler = $task['HANDLER'];
        $payload = json_decode($task['PAYLOAD'], true);
        call_user_func($handler, $payload);

        $connection->queryExecute("DELETE FROM project_queue WHERE ID = " . (int)$task['ID']);
    } catch (\Throwable $e) {
        $connection->queryExecute(sprintf(
            "UPDATE project_queue SET STATUS = 'new', ATTEMPTS = ATTEMPTS + 1 WHERE ID = %d",
            (int)$task['ID']
        ));
    }
}

Pros of the queue table: minimal infrastructure, full control, easy to debug. Cons: does not scale horizontally, no priorities, no dead letter queue, and under high loads the table becomes a bottleneck.

Third-party brokers. Bitrix has no stable public API for production queues. The core has the Bitrix\Main\Messenger namespace, but it relates to internal framework mechanisms, is poorly documented, and may change between versions. Using it in production as of 2026 is risky.

For high-load projects, the industry standard is Symfony Messenger with a RabbitMQ or Redis transport. There are ready-made modules that integrate it into Bitrix without rewriting the core. Specific packages and their relevance should be checked at the time of adoption: the ecosystem of third-party Bitrix modules changes, and not all solutions support PHP 8.2/8.3 and current core releases.

Pros of third-party brokers: production-grade reliability, scaling, priorities, dead letter queue, delayed messages. Cons: additional infrastructure, you need to understand AMQP, you need Supervisor for workers, you need queue monitoring. For projects with tens of thousands of tasks per hour, RabbitMQ is justified; for small projects, a queue table is enough.

Part 3. Layout and Templating

Architecture of local/templates

A site template is not a place for business logic. In header.php, only stylesheet inclusion, header output, and container opening. If header.php contains a call to CIBlockElement::GetList, that is a mistake. Data is prepared in components; the template only displays it. Styles are split by component and included via Asset::addCss() in component_epilog.php.

Fluid Layout and Container Queries

Instead of hundreds of @media queries — clamp() for typography and spacing, Container Queries for style isolation. A product card in a catalog grid and in a slider is one component that adapts to the parent's width. The container is placed on the wrapper of each card, not on the overall grid.

.product-card {
    container-type: inline-size;
    container-name: product-card;
}

@container product-card (min-width: 400px) {
    .product-card__inner {
        flex-direction: row;
    }
}

Pros: components are reused without edits, no JS hacks for measuring block width, fewer media queries. Cons: Container Queries support is only in modern browsers (95%+ as of 2026); older browsers will get a fixed layout. For Bitrix projects with a B2B audience where outdated browsers still appear, progressive enhancement is required.

Templating Engines: Twig in Bitrix

Bitrix supports third-party templating engines only for component templates. Connecting Twig via arCustomTemplateEngines.

Pros: concise syntax, clear separation of logic and presentation, protection against accidental calls to the Bitrix API. Cons: an additional layer of abstraction, slightly lower performance, developers need to learn the syntax. For a team of several developers, Twig is justified; for a single developer, the native template.php is simpler.

Part 4. Code Quality

PHPStan: Static Analysis

parameters:
    level: 5
    paths:
        - local/lib
    scanDirectories:
        - bitrix/modules
    ignoreErrors:
        - '#Call to an undefined method Bitrix\\Main\\Entity\\DataManager::#'

Pros: catches errors before runtime, helps with refactoring. Cons: there are many false positives on Bitrix due to dynamic methods; some errors have to be ignored. Level 5 is a reasonable compromise between usefulness and noise.

PHP-CS-Fixer and EditorConfig

PHP-CS-Fixer brings code to a unified style via .php-cs-fixer.php. EditorConfig configures indentation at the editor level. Pros: unified style, fewer arguments in review, clean diff history. Cons: auto-running on CI can "touch" other people's commits; it is better to run it via a pre-commit hook.

Tests: PHPUnit and Codeception

class OrderServiceTest extends BitrixTestCase
{
    public function testCreateOrder(): void
    {
        $service = new OrderService();
        $result = $service->create(['productId' => 1, 'quantity' => 2]);

        $this->assertInstanceOf(Order::class, $result);
    }
}

Important: BitrixTestCase is not a built-in Bitrix class. It usually comes from third-party packages, such as webarchitect609/bitrix-taxidermist, and requires separate configuration. Check the package's relevance for your PHP and core version.

Pros: regression is covered, refactoring is safe, documentation through tests. Cons: Bitrix is poorly isolated for unit tests; third-party solutions have to be used, which complicates setup. For small projects, full test coverage is often not cost-effective.

Part 5. Monitoring and Logging

Grafana + Prometheus + Sentry

Monitoring should show not only "hardware" but also business metrics: TTFB, request count, errors. Grafana and Prometheus provide a unified dashboard. Sentry catches exceptions in real time.

Pros: a single point of truth, change history, alerts, fast diagnostics. Cons: the infrastructure requires a separate server; setup takes time. For small projects, Sentry and built-in metrics are often enough.

Monolog: Logging in Bitrix

$logger = new \Monolog\Logger('app');
$logger->pushHandler(new \Monolog\Handler\RotatingFileHandler(
    $_SERVER['DOCUMENT_ROOT'] . '/local/logs/app.log',
    30,
    \Monolog\Logger::INFO
));

$logger->info('Order created', ['orderId' => $orderId]);

Pros: structured logs, rotation, separate channels. Cons: if you write a log to a file on every operation, the disk will fill up within a week. Rotation, limits, and Sentry for errors are needed.

Part 6. Security

SQL Injection, XSS, CSRF

Always use SqlHelper::forSql() or parameterized queries. For numeric values — cast to int. All data in templates — via htmlspecialcharsbx(). In forms — bitrix_sessid_post(), on receipt — check_bitrix_sessid().

CSP and HTTP Security Headers

Content Security Policy for Bitrix cannot be enabled "head-on" with unsafe-inline and unsafe-eval — that is not CSP, but an imitation of it. The core and most Bitrix modules use inline scripts and dynamic code generation, so a strict policy breaks the admin panel and some public pages.

The right approach is to start with Content-Security-Policy-Report-Only mode, collect reports on which sources and inline scripts are actually used, and only then enable the live header with nonce or hashes for inline code. To receive reports, use report-to (the modern replacement for the deprecated report-uri) and a dedicated endpoint. In practice, for Bitrix, people often limit themselves to frame-ancestors 'self' and upgrade-insecure-requests, moving full CSP to a separate task.

# Individual headers that are safe for Bitrix out of the box
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header Referrer-Policy "strict-origin-when-cross-origin";

# CSP in report-only mode, without blocking
add_header Content-Security-Policy-Report-Only "default-src 'self'; report-to csp-endpoint";

Pros: protection against XSS, clickjacking, referrer leakage. Cons: strict CSP breaks some Bitrix modules — you will have to allow specific external domains, which weakens protection. It is configured carefully and tested in report-only mode before being enabled.

Cookies with HttpOnly, Secure, SameSite Flags

The Bitrix session cookie must have the HttpOnly, Secure, and SameSite=Lax or Strict flags. Without them, a Bitrix online store is vulnerable to session hijacking. Configured in php.ini via session.cookie_httponly and session.cookie_secure.

File Permissions, Closed /bitrix

Base permissions: 644 for files, 755 for directories. Directories that the web server must write to (/upload, /bitrix/cache, /bitrix/managed_cache, /bitrix/html_pages, /local/logs) require separate owner and permission configuration — usually their owner becomes the www-data or nginx user. The dbconn.php file must not be accessible from outside.

You cannot close /bitrix entirely — static file serving will break (/bitrix/js, /bitrix/css, /bitrix/images, /bitrix/templates). The right approach: disable PHP execution inside /bitrix, except for allowed entry points (/bitrix/admin, /bitrix/urlrewrite.php, some service scripts), while keeping static files accessible.

# Disable PHP execution in /bitrix, except for allowed entry points
location ~ ^/bitrix/(?!admin|urlrewrite\.php|tools/).*\.php$ {
    deny all;
    return 403;
}

# Serve static files from /bitrix
location ~* ^/bitrix/.*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
    expires 30d;
    access_log off;
}

Part 7. SEO

SEO-Friendly URLs, search Module

SEO-friendly URLs are configured via urlrewrite.php and rules in components. The search module handles indexing and full-text search. Pro: correct URLs are laid down from the start. Con: changing them later is painful; all links will be broken.

sitemap.xml and robots.txt

sitemap.xml is generated by the search module or third-party solutions and split into files of 50,000 URLs each. robots.txt closes technical sections and opens the catalog.

Schema.org Microdata

For JSON-LD, you cannot use htmlspecialcharsbx(): it converts quotes and special characters into HTML entities, making the JSON invalid or distorted. Use json_encode():

&lt;?php
$schema = [
    '@context' => 'https://schema.org',
    '@type' => 'Product',
    'name' => $arResult['NAME'],
    'offers' => [
        '@type' => 'Offer',
        'price' => $arResult['PRICE'],
        'priceCurrency' => 'RUB',
    ],
];
?&gt;
&lt;script type="application/ld+json"&gt;
&lt;?= json_encode($schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?&gt;
&lt;/script&gt;

Pros: improves snippets, increases CTR. Cons: markup errors are caught via Google Search Console only after publication. Schema.org rules for Product change regularly.

Part 8. Integrations: 1C and Modern CRMs

For a Bitrix online store, integrations are half the value. Without 1C exchange, the catalog and stock will diverge from reality; without CRM, work with the customer base, the sales funnel, and marketing are lost. Below are the basic scenarios found in almost every project.

1C Integration: CommerceML, REST API, Middleware Bus

Three scenarios for exchange with 1C: UT, ERP, KA:

CommerceML — the standard Bitrix mechanism. Exports the catalog, prices, stock, and orders via exchange1c.
Pros: works out of the box, documentation, vendor support.
Cons: slow on large catalogs, exchange on a schedule (not real-time), handles complex property and SKU structures poorly, format customization is limited.

1C REST API (via HTTP services or OData).
Pros: flexibility, you can request only the data you need, real-time stock exchange, easy to connect to queues.
Cons: requires modification on the 1C side (not all configurations have ready-made services), needs a stable channel, needs middleware if there are more than two systems.

Middleware bus (RabbitMQ, Kafka, 1C:Shina).
Pros: scales, decouples systems, guarantees delivery, easy to add new consumers.
Cons: expensive in infrastructure, requires a team that knows how to maintain it. Justified only for large projects with multiple systems.

At the design stage, it is important to define master data: prices and stock — in 1C, content — on the site, orders — on the site, exported to 1C. If this is not fixed, in a year the catalog will have to be rewritten.

Bitrix24: CRM and Team Portal

Bitrix24 is both a CRM and a corporate portal, a lead generator, and a deal repository. Integration with a Bitrix site is usually done via REST API and webhooks.

What it gives: leads from site forms go into the CRM automatically, deals are synchronized with orders, the customer base is combined with purchase history, the funnel is built on actual orders, end-to-end analytics from advertising to revenue.

Pros: native integration with Bitrix, a single ecosystem, ready-made REST methods, built-in phone and email, no need for a separate server. Cons: tariffs grow with the number of users, some integrations require the "on-premise" version, customization is limited by the platform's framework. For projects where the team already works in Bitrix24, integration pays off quickly.

RetailCRM: Specialized CRM for E-commerce

RetailCRM is a CRM tailored for online stores and omnichannel commerce. It works with orders, warehouse, delivery, and marketing.

What it gives: a single customer card with all orders, integration with dozens of delivery services and payment systems, automated triggered mailings, a loyalty program, end-to-end analytics, an API with limits designed for online store loads.

Pros: tailored for e-commerce, integration with Bitrix via a ready-made module, many ready-made automation scenarios, support for SMS, email, messengers, subscription fee not tied to the number of users. Cons: does not replace a corporate portal, limited capabilities for non-commerce processes, slightly less flexible customization than Bitrix24. For a mid-sized Bitrix online store with 100+ orders per day, RetailCRM pays off through automation.

AmoCRM: Sales Funnel and Lead Generation

AmoCRM is a CRM with a strong sales funnel, popular in B2B and services. Integration with the site is via REST API and webhooks; there are ready-made modules for Bitrix.

What it gives: a flexible funnel with automation, work with leads from different sources, a digital funnel with triggers, integration with telephony, conversion reports at each stage.

Pros: one of the best funnels on the market, many ready-made integrations, flexible digital funnels, a wide partner network of implementers. Cons: payment per user, limits on the number of deals on tariffs; for an online store with a large order flow, tariffs quickly become expensive. AmoCRM is justified in projects where sales go through managers rather than through a shopping cart: B2B, services, complex products.

Which CRM to Choose

  • Bitrix24 — if the team already works in the Bitrix ecosystem and needs a portal, CRM, and accounting in one place.
  • RetailCRM — if it is an online store with an order flow and delivery integrations, marketing, and loyalty programs matter.
  • AmoCRM — if sales go through managers, the deal cycle is long, and B2B and services are important.

In all three cases, integration with a Bitrix site is built via REST API. Orders and leads are passed to the CRM, statuses and payments are returned, and purchase history is pulled into the customer card. This is a standard pattern that should be laid down at the design stage, not after launch.

Part 9. Practice

What Most Often Breaks During Implementation

First — the composite cache. It is enabled, but the serving block is not added in Nginx — no effect. Second — queues: a third-party broker is installed, but Supervisor is forgotten, and workers crash on the first failure. Third — migrations: they are applied manually on staging, forgotten on production, and the schema diverges. Fourth — CSP: it is enabled strictly with unsafe-inline, thinking they are protected, but in fact it is not CSP. Fifth — backups on the same disk as the site. Sixth — CRM integration without a queue: during an order spike, the API drops due to limits, and orders are lost. Seventh — rsync --delete without exclusions wipes out /bitrix and /upload on the very first deployment. Eighth — client_max_body_size is forgotten, and file uploads through the admin panel silently fail at 4 MB.

How to Verify Everything Works

Running through the Bitrix Quality Monitor is a mandatory minimum. It checks the web environment, design integration, security, performance, and hosting placement. Separately, verify that agents run via cron, not on hits; that /bitrix is not in Git; that dbconn.php does not contain live passwords. Integrations are tested on real data: a test order goes to the CRM, synchronization returns a status, and the queue does not lose messages when the worker restarts.

Summary and Pre-Launch Checklist

Bitrix development without legacy is about infrastructure that allows the project to evolve for years. A server on a clean stack, Docker, Git with only project code, Composer, D7 ORM and Highload blocks, manual indexes, fluid layout, integrations with 1C and CRM, agents on cron, queues, CI/CD, tests, monitoring, security, SEO.

  • Infrastructure: clean stack Nginx + PHP-FPM 8.2/8.3 + Percona; composite served via Nginx; /bitrix/html_pages/ closed from direct access; client_max_body_size raised to 128 MB; static files cached.
  • Docker: docker-compose.yml for local development; separate docker-compose.prod.yml for production.
  • Git: only /local, configs, and composer.json; core and /upload in .gitignore; .env not committed.
  • Branching: master for production, develop for staging, feature/* for tasks.
  • CI/CD: build, test, deploy pipeline; rsync over SSH with exclusions for /bitrix, /upload, .env.
  • Backup: daily mysqldump with --single-transaction; file copy; retention for 7–30 days on a separate server.
  • Database: Highload blocks for flat directories; DataManager for tables with composite indexes; migrations via MigrationRunner with a version table.
  • Queues: queue table for simple tasks; third-party broker (Symfony Messenger + RabbitMQ) for high loads; workers under Supervisor.
  • Layout: clamp() and Container Queries; container on the wrapper of each card.
  • Code quality: PHPStan, PHP-CS-Fixer, EditorConfig; tests via PHPUnit and Codeception.
  • Monitoring: Grafana + Prometheus, Sentry, Monolog.
  • Security: input escaping, bitrix_sessid_post(), CSP in report mode, HttpOnly/Secure/SameSite flags, /bitrix closed to PHP execution.
  • SEO: SEO-friendly URLs, sitemap.xml, robots.txt, Schema.org via json_encode().
  • Integrations: 1C via CommerceML or REST API; CRM (Bitrix24, RetailCRM, AmoCRM) via REST API and webhooks; queues for exchange resilience.
  • Quality Monitor: mandatory tests passed; critical security errors eliminated.

The material turned out to be an overview — each topic deserves its own article. From here, the decision is yours: go deeper into a specific direction, build a full engineering guide on it, or keep it as a brief reference for deploying Bitrix projects.

Aleksandr Tichiy
Author

Aleksandr Tichiy

Fullstack developer · 1C-Bitrix

I build and modernize online stores and corporate portals on 1C-Bitrix. Integrations with 1C, high-load performance, 152-FZ compliance. Writing about architecture and e-commerce.


Comments

There are no comments yet, you can be the first to leave it

Leave a comment

The site uses a comment pre-moderation system, so your message will be published only after approval by the moderator

You are replying to a user's comment

Send

FEEDBACK

Email me

Please message me to discuss the task. I work remotely and officially (as a self-employed professional); my hourly rate is 1,800 RUB, and I can dedicate 3–4 hours a day to the project. I respond quickly, and the consultation is free.

An error occurred while sending, please try again after a while
Message sent successfully

Phones

+7(993) 007-18-96

Email

info@tichiy.ru

Address

Россия, г. Москва

By submitting the form, you automatically confirm that you have read and accept the Privacy Policy site