MAATRIX / Блог / Strapi на сервере: частые ошибки и решения

Strapi на сервере: частые ошибки и решения

MAATRIX

Strapi — самый популярный open-source headless CMS на Node.js, и именно его гибкость чаще всего подводит на проде: приложение либо не запускается после деплоя, либо падает через пару часов работы, либо отдаёт 502 через reverse proxy. Собрал здесь конкретные симптомы, с которыми реально сталкивался на VPS, и рабочие решения — без теории, сразу к делу.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Strapi не стартует после деплоя

Первое, с чем сталкивается почти каждый: локально всё работает, на сервере npm run start падает с ошибкой или зависает.

Проверьте версию Node.js. Strapi жёстко завязан на конкретный диапазон версий Node — несовпадение даёт либо явную ошибку в консоли, либо труднообъяснимые сбои сборки админки. Смотрите требование в package.json вашего проекта, поле engines:

cat package.json | grep -A 2 '"engines"'
node -v

Если версии не совпадают, ставьте нужную через nvm, не полагайтесь на системный Node из репозитория дистрибутива — там часто устаревшая ветка:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install 20
nvm use 20
nvm alias default 20

Проверьте, что сборка админки прошла. Strapi требует собранную папку build для продакшен-режима:

NODE_ENV=production npm run build
NODE_ENV=production npm run start

Если пропустить build и сразу запустить start в production-режиме, получите ошибку про отсутствующий build/index.html или белый экран в админке при рабочем API.

Проверьте переменные окружения. Strapi требует набор секретов в .env, и без них процесс либо не стартует, либо стартует с предупреждениями о небезопасной конфигурации:

APP_KEYS=key1,key2,key3,key4
API_TOKEN_SALT=...
ADMIN_JWT_SECRET=...
TRANSFER_TOKEN_SALT=...
JWT_SECRET=...

Сгенерировать случайные значения:

node -e "console.log(require('crypto').randomBytes(16).toString('base64'))"

Повторите команду для каждого секрета — использовать один и тот же ключ для разных переменных нельзя, это ослабляет всю схему подписи токенов.

Ошибки подключения к базе данных

Strapi по умолчанию в dev-режиме использует SQLite, но на проде почти всегда стоит переходить на PostgreSQL — SQLite не держит параллельные записи под нагрузкой и плохо переживает бэкапы «на живую».

Типичная ошибка при переходе:

error: connect ECONNREFUSED 127.0.0.1:5432

Разберите по шагам:

  1. Служба PostgreSQL вообще запущена: systemctl status postgresql.
  2. Строка подключения в config/database.ts (или .js) действительно читает переменные из .env, а не хардкод из шаблона по умолчанию.
  3. Пользователь БД и пароль совпадают с тем, что реально создано в PostgreSQL — частая ошибка password authentication failed for user возникает, когда .env правили, а пользователя в базе не пересоздавали.

Минимальный конфиг под PostgreSQL:

DATABASE_CLIENT=postgres
DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
DATABASE_NAME=strapi_db
DATABASE_USERNAME=strapi_user
DATABASE_PASSWORD=strong_password_here
DATABASE_SSL=false

Если ставите PostgreSQL с нуля, я подробно разбирал процесс в статье про установку PostgreSQL на VPS, а частые проблемы конкретно с подключениями — в материале PostgreSQL не принимает подключения. Там же нюанс с pg_hba.conf, который часто и есть причина ECONNREFUSED, если сама служба запущена, но слушает не тот интерфейс.

Отдельно — не забывайте про лимит подключений. Strapi под нагрузкой держит пул соединений к БД, и при плохо настроенном pool в конфиге можно упереться в too many connections:

export default ({ env }) => ({
  connection: {
    client: 'postgres',
    connection: {
      host: env('DATABASE_HOST'),
      port: env.int('DATABASE_PORT', 5432),
      database: env('DATABASE_NAME'),
      user: env('DATABASE_USERNAME'),
      password: env('DATABASE_PASSWORD'),
    },
    pool: { min: 2, max: 10 },
  },
});

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Strapi падает по нехватке памяти

Это, пожалуй, самая частая причина нестабильной работы на бюджетных VPS. Strapi на старте компилирует схему контент-типов и держит в памяти немало служебных данных, плюс сборка админки — процесс сама по себе прожорливая по RAM.

Симптом в логах — процесс просто исчезает без внятной ошибки, а в dmesg находится:

dmesg | grep -i "killed process"

Если видите Out of memory: Killed process ... (node) — это OOM-killer ядра Linux, приложению банально не хватило памяти.

Точных цифр «сколько RAM нужно Strapi» я вам не назову — сильно зависит от количества контент-типов, плагинов и от того, идёт ли сборка админки на этом же сервере. Ориентировочно: для стабильной работы небольшого проекта закладывайте от 1 ГБ RAM, а если на том же сервере планируете собирать npm run build (а не приносить уже собранную папку build из CI), с запасом лучше 2 ГБ — процесс сборки съедает заметно больше, чем сам работающий сервер.

Практичные меры:

  • Собирайте админку не на проде. Соберите build локально или в CI, залейте на сервер уже готовую папку — так продовый процесс сборки не съедает память боевого окружения.
  • Добавьте swap, если память впритык. Это не панацея, но подстрахует от внезапного OOM-килла в момент пиковой нагрузки — подробно про настройку писал в статье про swap-файл на VPS.
  • Ограничьте память Node явно, чтобы процесс падал предсказуемо и перезапускался, а не тянул систему в OOM:
NODE_OPTIONS="--max-old-space-size=768" npm run start

Строгий деплой процесса: почему нельзя просто npm run start

Запускать Strapi через npm run start в обычной SSH-сессии — гарантированный способ потерять приложение при разрыве соединения. Нужен менеджер процессов, который держит Strapi живым и поднимает после падения или ребута сервера.

Вариант с systemd (без лишних зависимостей):

# /etc/systemd/system/strapi.service
[Unit]
Description=Strapi CMS
After=network.target postgresql.service

[Service]
Type=simple
User=strapi
WorkingDirectory=/var/www/strapi-app
ExecStart=/usr/bin/node /var/www/strapi-app/node_modules/.bin/strapi start
Restart=on-failure
RestartSec=5
EnvironmentFile=/var/www/strapi-app/.env

[Install]
WantedBy=multi-user.target
systemctl daemon-reload && systemctl enable --now strapi
journalctl -u strapi -f

Вариант с PM2 (удобнее для просмотра логов и мониторинга ресурсов налету):

npm install -g pm2
pm2 start npm --name strapi -- run start
pm2 save
pm2 startup

pm2 startup выдаст команду для регистрации автозапуска — обязательно выполните её, иначе после перезагрузки сервера Strapi не поднимется сам.

Частая ошибка с PM2: процесс запущен под одним пользователем, а pm2 startup выполнили под другим (например, стартовали как root, а деплоите под непривилегированным пользователем) — автозапуск после ребута не срабатывает. Проверяйте, что pm2 save и pm2 startup выполнены от одного и того же пользователя, под которым реально крутится процесс.

Reverse proxy: 502 и проблемы с загрузкой файлов

Strapi слушает 1337 порт напрямую, наружу его лучше не выставлять — ставьте перед ним nginx как reverse proxy с SSL.

Базовый конфиг:

server {
    listen 80;
    server_name cms.example.com;

    location / {
        proxy_pass http://127.0.0.1:1337;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}

Если получаете 502, первым делом проверяйте, что сам процесс Strapi живой:

curl -I http://127.0.0.1:1337/admin
systemctl status strapi   # или pm2 status

Если процесс жив, но 502 всё равно есть — смотрите таймауты. Strapi при первом запросе после простоя иногда «прогревается» дольше стандартного таймаута nginx, особенно если БД на другом сервере. Увеличьте:

proxy_connect_timeout 60s;
proxy_send_timeout    60s;
proxy_read_timeout    60s;

Общий разбор причин 502 через nginx — в статье 502 Bad Gateway в nginx, там же чек-лист диагностики, применимый к любому Node-приложению за прокси, не только к Strapi.

Отдельная головная боль — загрузка медиафайлов. По умолчанию nginx режет тело запроса в 1 МБ, и загрузка изображений в Strapi Media Library падает с ошибкой 413:

client_max_body_size 50M;

Добавьте эту директиву в блок server или location, значение подберите под реальные файлы, которые загружают редакторы.

Если разворачиваете Strapi с нуля и ещё не настраивали прокси, вся пошаговая настройка — в статье Nginx как reverse proxy на VPS, а частые проблемы конкретно с прокси-конфигом — в материале Nginx как reverse proxy: частые ошибки.

CORS и проблемы доступа из фронтенда

Если фронтенд (Next.js, Nuxt, отдельный SPA) обращается к Strapi API с другого домена, получите классическую ошибку в консоли браузера:

Access to fetch at 'https://cms.example.com/api/articles' from origin 'https://example.com'
has been blocked by CORS policy

Strapi настраивает CORS через middleware, файл config/middlewares.ts:

export default [
  'strapi::errors',
  {
    name: 'strapi::cors',
    config: {
      origin: ['https://example.com', 'https://www.example.com'],
    },
  },
  'strapi::security',
  // остальные middleware
];

Частая ошибка — забыть, что strapi::security middleware по умолчанию блокирует загрузку изображений с других доменов через Content-Security-Policy. Если картинки из Media Library не грузятся на фронте при настроенном CORS, добавьте домен фронтенда в директиву img-src конфига strapi::security в том же файле middlewares.

Права доступа, роли и permissions API возвращает 403

После установки чистого Strapi публичный API по умолчанию закрыт — это правильно с точки зрения безопасности, но неожиданно для новичков: запрос к /api/articles возвращает 403 Forbidden, хотя контент-тип создан и данные есть.

Откройте Settings → Users & Permissions Plugin → Roles → Public и явно разрешите нужные действия (find, findOne) для каждого контент-типа, который должен быть доступен без авторизации. Это делается через админку, но если нужна автоматизация (например, при деплое через CI), права можно выставить и через seed-скрипт, который дергает Strapi API с токеном администратора при первом запуске.

Отдельная ловушка: роль Authenticated и роль Public — разные сущности. Если тестируете API с JWT-токеном залогиненного пользователя, а права выданы только Public — тоже получите 403, хотя запрос вроде бы «авторизованный».

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

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

Можно ли держать Strapi и PostgreSQL на одном VPS?

Да, для небольших и средних проектов это нормально, если у сервера достаточно RAM с запасом под оба процесса и под сборку админки. Для нагруженных проектов вынесите БД на отдельный сервер или управляемую базу — так проще масштабировать независимо.

Почему после git pull и рестарта Strapi показывает старую версию админки?

Потому что вы не пересобрали build. После любых изменений в схеме или конфиге, влияющих на админ-панель, нужно заново npm run build в production-режиме, иначе сервер продолжает отдавать старую собранную статику.

Как понять, что причина падения — именно нехватка памяти, а не баг в коде?

Смотрите dmesg | grep -i "killed process" сразу после падения. Если ядро зафиксировало OOM-killer именно на процессе node — это память. Если процесс упал с трассировкой стека в journalctl -u strapi или в логах PM2 — ищите баг в коде или в конфигурации плагина.

Нужен ли для Strapi обязательно PostgreSQL, или можно остаться на SQLite в проде?

Технически можно, но не рекомендую для реального проекта с несколькими одновременными редакторами — SQLite блокирует базу на запись, и параллельные операции начинают конфликтовать. PostgreSQL для прода — практически обязательный переход.

Что делать, если после обновления Strapi до новой мажорной версии всё сломалось?

Обязательно смотрите официальный migration guide для конкретной версии перед обновлением на проде — мажорные релизы Strapi нередко меняют структуру конфигов и требуют миграции базы. Обновляйте сначала на staging-копии сервера, а не сразу на боевом.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →