Выбор площадки: shared, VPS или свой сервер
В обзорной статье серии я объяснял, почему мы предлагаем клиентам OpenCart. Сегодня — практика: я, как техдиректор ITfresh, покажу наш внутренний регламент развёртывания, по которому инженеры проходят путь от пустого VPS до боевой витрины. Все команды и значения ниже — не «примерные», а те, что реально стоят на магазинах, которые мы сопровождаем.
Первое решение — где магазину жить. OpenCart нетребователен, и это надо использовать, а не перестраховываться «сервером с запасом в десять раз». Наши ориентиры:
| Каталог | Площадка | Почему |
|---|---|---|
| До 2–3 тыс. SKU | shared-хостинг или минимальный VPS 1 vCPU / 1–2 ГБ | движку хватает; экономия важнее контроля |
| 3–15 тыс. SKU | VPS 2 vCPU / 4 ГБ RAM, NVMe | наш основной сценарий — весь этот регламент писан под него |
| От ~20 тыс. SKU, сезонные пики | VPS 4+ vCPU, БД и кэш выносим отдельно | узкое место — MariaDB и генерация кэша картинок |
Когда мы настаиваем именно на VPS, а не на shared: если планируется обмен с 1С (нужны свои cron и лимиты PHP), если каталог больше трёх тысяч позиций, и если клиент хочет предсказуемости — на shared сосед по серверу может в любой день устроить вам деградацию, а поддержка хостера разведёт руками. Свои магазины мы размещаем на собственных серверах в дата-центре МТС: гипервизор наш, диски наши, и когда клиент звонит «сайт тормозит», мы смотрим метрики, а не пишем тикет чужой поддержке.
Операционная система — Ubuntu 24.04 LTS. Не потому, что она чем-то волшебна, а потому, что в её штатных репозиториях лежит PHP 8.3, поддержка до 2029 года, и весь наш парк унифицирован: один регламент обновлений на все машины. Дальше по тексту все команды — под неё.
Готовим стек: nginx + PHP-FPM 8.3 + MariaDB
Официальный минимум для четвёртой ветки — PHP 8.1, а релиз 4.1.0.4 от 11 августа 2026 года официально дружит и с PHP 8.5. Мы ставим 8.3 из штатного репозитория Ubuntu 24.04: без сторонних PPA, обновления приходят вместе с системой. Гнаться за 8.5 на бою пока незачем — сторонние модули под неё ещё не все причёсаны.
Пакеты и PHP-модули
Инсталлятор OpenCart на втором шаге сам проверяет расширения и не пустит дальше, если чего-то нет. Обязательный список: cURL, GD, ZIP, Zlib, mbstring, OpenSSL плюс драйвер MySQL. Ставим всё разом:
apt update
apt install nginx mariadb-server \
php8.3-fpm php8.3-mysql php8.3-curl php8.3-gd \
php8.3-zip php8.3-mbstring php8.3-xml php8.3-intl
mysql_secure_installation
php8.3-mysql приносит и mysqli, и pdo_mysql — инсталлятору хватит любого; intl и xml формально не обязательны, но без них спотыкаются половина модулей, так что мы включили их в стандарт. OpenSSL и Zlib в убунтовской сборке PHP уже вкомпилированы.
Server-блок nginx
OpenCart из коробки заточен под Apache с .htaccess, где правило ЧПУ переписывает всё на index.php?_route_=…. Под nginx это одна строка в try_files. Наш боевой шаблон, сокращённый до сути:
server {
listen 80;
server_name shop.example.ru;
root /var/www/shop/public;
index index.php;
client_max_body_size 64m;
location / {
try_files $uri $uri/ /index.php?_route_=$uri;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
# служебные каталоги наружу не отдаём
location ~ ^/(system|storage)/ { deny all; }
location ~ /\. { deny all; }
location ~* \.(webp|jpg|png|svg|css|js|woff2)$ {
expires 30d;
access_log off;
}
}
client_max_body_size 64m — не прихоть: через админку загружаются архивы модулей и пачки изображений, и дефолтный 1 МБ nginx режет их с ошибкой 413. Запрет на system/ обязателен: там лежат логи и код, которым в браузере делать нечего.
Тюнинг PHP-FPM и OPcache
Дефолтный пул FPM рассчитан на «сайт-визитку». Для каталога 5–10 тыс. SKU на VPS 2 vCPU / 4 ГБ мы ставим в /etc/php/8.3/fpm/pool.d/www.conf:
pm = dynamic
pm.max_children = 12
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500
Арифметика простая: воркер OpenCart под нагрузкой ест 60–90 МБ, двенадцать воркеров — около гигабайта, ещё гигабайт-полтора оставляем MariaDB, остальное — системе и файловому кэшу. Поставите max_children «побольше, чтоб было» — под пиком сервер уйдёт в swap, и это хуже, чем очередь запросов. pm.max_requests = 500 страхует от утечек памяти в кривых модулях: воркер перезапускается сам.
В /etc/php/8.3/fpm/conf.d/99-opcache.ini:
opcache.memory_consumption = 192
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 1
opcache.revalidate_freq = 60
У OpenCart 4 с Twig-шаблонами и модулями набегают тысячи PHP-файлов — дефолтных 128 МБ кэша опкодов впритык, 192 МБ хватает с запасом. Timestamps мы не отключаем: выигрыш микроскопический, а забытый сброс кэша после правки модуля стоил одному нашему инженеру часа недоумения.
Установка: от архива до мастера
Релизы живут на GitHub в репозитории opencart/opencart — оттуда и берём, никаких «сборок с файлопомоек». Скачиваем архив свежего релиза 4.1.0.4, содержимое каталога upload/ кладём в webroot и сразу активируем конфиги-заготовки:
cd /var/www/shop
unzip opencart-4.1.0.4.zip
mv upload public
cd public
mv config-dist.php config.php
mv admin/config-dist.php admin/config.php
chown -R www-data:www-data /var/www/shop
База и пользователь — с минимальными правами и только с localhost. Права SUPER, FILE и прочая экзотика магазину не нужны, а при взломе сайта урезанный пользователь БД сильно ограничивает ущерб:
CREATE DATABASE shop CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'shop'@'localhost' IDENTIFIED BY 'длинный_пароль';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER,
INDEX, DROP, LOCK TABLES ON shop.* TO 'shop'@'localhost';
Дальше — веб-инсталлятор: открываем сайт в браузере, соглашаемся с лицензией, проходим проверку расширений (если готовили сервер по прошлому разделу — она зелёная), вводим реквизиты БД и создаём администратора. Пять минут.
Пост-шаги, без которых в бой нельзя
Инсталлятор закончился — начинается настоящая работа. Три обязательных действия:
- Удалить
/install. Оставленный каталог инсталлятора — классическая дыра: любой желающий может «переустановить» вам магазин. OpenCart 4 сам напоминает об этом баннером — не игнорируйте. - Вынести
storage/за пределы webroot. Там лежат сессии, кэш, логи и загруженные файлы. Четвёрка предлагает перенос прямо в админке кнопкой; мы переносим в/var/www/shop/storage/— на уровень выше public. После переноса пути в обоихconfig.phpобновляются автоматически, но проверить константуDIR_STORAGEглазами — входит в наш чек-лист. Даже если когда-нибудь сломается запрет в nginx, файлы физически недостижимы по HTTP. - Переименовать каталог админки.
/admin— первое, куда стучатся брутфорс-боты. Переименовываем каталог во что-то своё (условно/backoffice-x7) и правим пути вadmin/config.php— там константы с путями и URL админки указаны явно, меняются заменой строки.
Грабли первого запуска
Права на запись. Самая частая проблема: картинки не загружаются, кэш не пишется. Каталоги image/ (вместе с image/cache/) и вынесенный storage/ должны принадлежать пользователю, под которым работает FPM: chown -R www-data:www-data плюс права 755 на каталоги и 644 на файлы. Ставить 777 «чтобы заработало» — запрещено нашим регламентом: это подарок первому же залитому шеллу.
Белый экран. Пустая страница после установки почти всегда означает фатальную ошибку PHP при выключенном выводе ошибок, и в девяти случаях из десяти — отсутствующее расширение, которое понадобилось модулю уже после инсталлятора. Диагноз ставится за минуту по логам: tail -f /var/log/php8.3-fpm.log и лог OpenCart в storage/logs/error.log. Приучите себя начинать с логов, а не с гадания — сэкономите часы.
Русификация: пакет на чистый OpenCart или сборка ocStore
Из коробки OpenCart англоязычен, и тут развилка, о которой я подробно писал в обзоре: русифицировать чистое ядро или взять готовую русскую сборку.
Путь 1: чистый OpenCart + языковой пакет
Берём бесплатный русский языковой пакет под свою ветку (для 4.x они живут в официальном маркетплейсе и на профильных русскоязычных форумах), загружаем через админку: Extensions → Installer, затем в System → Localisation → Languages добавляем русский с кодом ru-ru. Дальше в настройках магазина переключаем язык витрины и админки по умолчанию. Отдельно проверьте валюту (рубль с правильным форматом), часовой пояс и список стран в форме заказа: покупателю из Тулы незачем листать выпадашку из двухсот государств.
Путь 2: сборка ocStore
ocStore — это тот же OpenCart, но с русским языком, ЧПУ и модулями под РФ из коробки. Ставится так же: архив, инсталлятор, те же пост-шаги. Существенный нюанс 2026 года: актуальный ocStore — это ветка 3.0.4.x, то есть по коду он следует за «тройкой», а не за четвёркой. Выбирая сборку, вы выбираете и legacy-ветку — живую и поддерживаемую (3.0.5.1 вышла в один день с 4.1.0.4), но архитектурно вчерашнюю.
Как выбираем мы
Сборка быстрее на старте: день экономии на русификации и типовых модулях — честная цифра из нашей практики. Но чистое ядро проще обновлять: между вашим магазином и апстримом нет чужой прослойки, каждый релиз OpenCart накатывается по официальной процедуре, а не «ждём, пока авторы сборки перенесут». Поэтому наше правило: если магазин строится на горизонт три года и больше — чистый OpenCart 4.x с языковым пакетом; сборка — когда дедлайн жёсткий, магазин типовой и заказчик осознанно принимает ветку 3.0.x. Обе конфигурации мы сопровождаем, обе рабочие — важно лишь, чтобы выбор был сделан с открытыми глазами, а не «так поставил прошлый подрядчик».
ЧПУ, SSL и базовое SEO при запуске
Магазин установлен и говорит по-русски — доводим URL и шифрование до боевого состояния.
ЧПУ. В настройках магазина (вкладка «Сервер») включаем Use SEO URLs. Дальше главное: у каждой категории, товара и информационной страницы должен быть заполнен SEO keyword — на четвёрке они редактируются в разделе Design → SEO URL централизованно. Два классических фейла, которые мы разгребали за другими: включили ЧПУ, а правило перезаписи на сервере не настроено — вся витрина отдаёт 404 (в нашем nginx-конфиге из второго раздела строка try_files … /index.php?_route_=$uri ровно про это); и неуникальные keyword у двух товаров — движок начинает отдавать не тот товар или ошибку. Правило: ЧПУ включается один раз перед запуском, до индексации, а не «потом как-нибудь».
SSL. Сертификат — Let's Encrypt через certbot. Грабля, на которую наступает каждый второй: инструкции из интернета предлагают certbot certonly --standalone, а standalone-режим поднимает собственный веб-сервер на 80-м порту — который уже занят вашим nginx, и certbot падает. Правильно — плагин nginx:
apt install certbot python3-certbot-nginx
certbot --nginx -d shop.example.ru --redirect
Плагин сам дополнит server-блок, выпустит сертификат, настроит 301-редирект с http на https и создаст таймер автопродления. После этого обязательно: в config.php и admin/config.php меняем константы HTTP-адресов на https-вариант — иначе часть ссылок и картинок останется незашифрованной и браузер будет ругаться на mixed content.
Минимальная SEO-гигиена. Перед открытием индексации: robots.txt с запретом служебных маршрутов (поиск, сортировки, сравнение — генераторы дублей) и ссылкой на карту сайта; штатный sitemap OpenCart включается как модуль фида и отдаёт актуальный XML сам. Про борьбу с дублями страниц и канониклы я подробно предупреждал в обзоре — здесь зафиксирую вывод: закладывайте SEO-модуль в смету сразу, при запуске это час работы, а после индексации кривых URL — недели переклейки редиректов.
Наполнение: импорт каталога, не убивая сервер
Витрина готова — пора заливать каталог. Руками через админку реально завести пару сотен товаров; на наших типовых пяти-десяти тысячах SKU нужен импорт.
Штатного полноценного импорта из CSV/Excel в ядре нет — это делается модулем (классика жанра — расширения типа Export/Import по XLSX-шаблону) либо, если источник — 1С, сразу модулем обмена, но это тема следующей статьи серии. Наш порядок при первичной загрузке:
- Сначала дерево категорий, потом товары — иначе половина позиций ляжет мимо разделов.
- Изображения заливаем не через веб-форму, а пакетно по SFTP в
image/catalog/, в импорт-файле указываем относительные пути. Загрузка тысяч картинок через браузер — мучение и таймауты. - Импорт гоняем частями по 500–1000 позиций и первый раз — на тестовой копии: одна кривая колонка в прайсе на десятой тысяче строк — и вы разбираетесь, что успело залиться, а что нет.
Лимиты PHP на время импорта. Дефолтные max_execution_time = 30 и memory_limit = 128M большой импорт не переживёт — скрипт умрёт на середине. На время загрузки поднимаем в пуле FPM лимиты до 300 секунд и 512 МБ, после импорта возвращаем обратно. Именно возвращаем: щедрые лимиты в проде маскируют проблемные модули, которые мы предпочитаем видеть в логах сразу.
Кэш изображений. Важная особенность, которую надо знать: OpenCart не хранит готовые превью — он генерирует их при первом обращении и складывает в image/cache/. Поэтому сразу после импорта витрина будет ощутимо тормозить: каждый первый показ категории — это ресайз десятков картинок силами GD. Это не поломка, это холодный кэш. Мы прогреваем его до анонса — обходчиком по собственной карте сайта:
wget -q --spider --recursive --level=3 \
--wait=0.5 https://shop.example.ru/
Полчаса-час фоновой прогулки по сайту — и к приходу живых покупателей все превью уже сгенерированы. Заодно такой обход — бесплатный смоук-тест: 404 и 500 в логе wget всплывают до того, как их увидит клиент.
Чек-лист перед запуском в бой
Финальный проход по нашему внутреннему чек-листу — инженер отмечает пункты письменно, «на глазок» не принимается:
- Каталог
/installудалён, админка переехала со стандартного пути, у администраторов — длинные уникальные пароли. storage/вынесен за webroot, права www-data проверены, в nginx закрытыsystem/и дот-файлы.- Вывод ошибок на экран выключен (display_errors = Off в FPM и «показывать ошибки» снято в настройках магазина), запись в лог — включена. Трейс ошибки на витрине — это и стыдно, и утечка путей сервера.
- Режим maintenance использовали по назначению: всё время наполнения магазин стоял закрытым от посетителей и роботов, и открываем его только этим пунктом, а не «он с первого дня торчал наружу пустым».
- Тестовый заказ по полному циклу: корзина → оформление → оплата тестовым платежом → смена статусов в админке → письма покупателю и менеджеру дошли. Не «страницы открываются», а именно сквозной заказ.
- Почта — через SMTP, а не через php mail(): в настройках магазина указываем SMTP-реквизиты ящика на домене. Письма с VPS через mail() без SPF/DKIM почти гарантированно едут в спам, а «клиент не получил подтверждение заказа» — худшая первая жалоба.
- Первичный бэкап снят и проверен: дамп MariaDB плюс файлы (webroot и вынесенный storage) уехали на другой сервер, и — ключевое — из этого бэкапа развёрнута контрольная копия. Непроверенный бэкап равен его отсутствию.
- Мониторинг подключён: у нас это Zabbix — доступность витрины по https, срок сертификата, место на диске, размер лога ошибок. Про алёрты и регламент эксплуатации будет отдельная статья серии.
Сколько это всё занимает. Типовое развёртывание по описанному регламенту — рабочий день-полтора инженера: два-три часа на сервер и стек, час на установку с пост-шагами, остальное — русификация, ЧПУ, SSL и прогон чек-листа. Отдельными строками в смете идут наполнение каталога (целиком зависит от качества исходного прайса — от пары часов до нескольких дней) и интеграции: платёжка, доставка, обмен с 1С. Про интеграции — в следующей статье; там же разберём переезд на OpenCart с других CMS.
Оставить комментарий