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

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

MAATRIX

Invoice Ninja выглядит просто в документации и превращается в источник загадочных ошибок на реальном сервере: белый экран после установки, счета зависают вместо автоматической отправки, PDF не генерируется или разъезжается вёрсткой, письма клиентам не доходят. Проблема почти всегда не в самом приложении, а в окружении вокруг него — правах на файлы, очередях задач, cron, PHP-расширениях и почте. Ниже — конкретные причины и рабочие решения с реальных self-hosted инсталляций.

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

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

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

Белый экран и 500-я ошибка сразу после установки

Самая частая точка провала — первый запуск. Invoice Ninja v5 построен на Laravel, и большинство «непонятных» ошибок на старте — это классические laravel-грабли.

Первое, что нужно проверить — версия PHP и набор расширений. Для актуальных версий Invoice Ninja нужен PHP 8.1 или новее и расширения bcmath, ctype, curl, dom, gd, mbstring, openssl, pdo, pdo_mysql, tokenizer, xml, zip, exif, gmp. Проверка одной командой:

php -v
php -m | grep -Ei 'bcmath|ctype|curl|dom|gd|mbstring|openssl|pdo_mysql|tokenizer|xml|zip|exif|gmp'

Если чего-то не хватает — на Ubuntu/Debian:

sudo apt update
sudo apt install php8.2-bcmath php8.2-ctype php8.2-curl php8.2-gd \
  php8.2-mbstring php8.2-mysql php8.2-xml php8.2-zip php8.2-exif php8.2-gmp
sudo systemctl restart php8.2-fpm

Дальше — файл .env. Пустой или неправильный APP_KEY даёт 500 Internal Server Error без внятного сообщения на экране (смотрите реальную причину в логах, не на странице браузера):

php artisan key:generate
tail -50 storage/logs/laravel.log

Если ключ на месте, а ошибка всё равно есть — почти наверняка это права на директории storage и bootstrap/cache. Laravel пишет туда логи, кэш и скомпилированные шаблоны, и веб-сервер должен иметь право записи:

sudo chown -R www-data:www-data storage bootstrap/cache
sudo find storage bootstrap/cache -type d -exec chmod 775 {} \;
sudo find storage bootstrap/cache -type f -exec chmod 664 {} \;

Если вы ставили через Docker (сейчас это самый популярный способ для Invoice Ninja) и получаете тот же белый экран — проверьте, что volume для storage действительно смонтирован, а не превратился в анонимный том при пересборке:

docker compose logs -f app
docker compose exec app php artisan config:clear

APP_URL, 419-я ошибка и проблемы за реверс-прокси

Если Invoice Ninja стоит за Nginx как реверс-прокси (а в 2026 году это почти всегда так — Docker-контейнер + Nginx + Let's Encrypt), классическая боль — форма логина падает с 419 Page Expired, а после логина куки постоянно слетают.

Причина в том, что Laravel сверяет домен из APP_URL с тем, что реально пришло в запросе, а за прокси эти значения могут расходиться. Проверьте .env:

APP_URL=https://invoice.example.com
SESSION_SECURE_COOKIE=true
SESSION_DOMAIN=.example.com

APP_URL должен быть именно https://, даже если приложение внутри слушает по http — TLS терминируется на Nginx. Если не указать, кому доверять как источнику реального протокола, Laravel будет считать все запросы http, и защищённые куки (SESSION_SECURE_COOKIE=true) просто не будут ставиться браузером. Добавьте доверенный прокси (TrustProxies / trustProxies(at: '*') в зависимости от версии) и убедитесь, что Nginx прокидывает реальные заголовки:

location / {
    proxy_pass http://127.0.0.1:8000;
    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;
}

Если SSL-сертификат ещё не выпущен или слетел — сначала разберитесь с этим, иначе всё остальное чинить бессмысленно: частые ошибки Let's Encrypt на сервере разбирают именно такие случаи с валидацией домена и продлением.

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

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

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

Повторяющиеся счета и напоминания не отправляются

Это, пожалуй, самая частая жалоба именно на Invoice Ninja конкретно — не общая для Laravel-приложений, а специфичная: администратор настраивает recurring invoice (повторяющийся счёт) или напоминание об оплате, а оно просто никогда не срабатывает. При этом сам интерфейс работает без ошибок.

Причина в 95% случаев — не запущен планировщик Laravel. Invoice Ninja не отправляет счета «по таймеру» изнутри PHP-процесса — за это отвечает php artisan schedule:run, который должен запускаться раз в минуту через cron и уже сам решает, какие задачи (генерация повторяющихся счетов, отправка напоминаний, чистка временных файлов) пора выполнить.

Проверьте crontab пользователя, от которого работает приложение (не root, а тот, под кем крутится веб-сервер, либо явно указанный в докер-образе):

crontab -u www-data -l

Если строки с schedule:run нет — добавьте:

* * * * * cd /var/www/invoiceninja && php artisan schedule:run >> /dev/null 2>&1

В официальном Docker-образе invoiceninja/invoiceninja планировщик обычно уже встроен в entrypoint, но если вы собирали свой docker-compose.yml на основе более старого примера — сверьтесь, что в нём есть отдельный сервис или cron-процесс внутри контейнера приложения, а не только php-fpm и nginx. Если такого сервиса нет — проще всего добавить рядом отдельный контейнер, который раз в минуту дёргает php artisan schedule:run на том же volume со storage.

Второй по частоте виновник — очереди (queue). Отправка писем, генерация PDF и часть фоновых задач в Invoice Ninja идут через очередь, а не выполняются синхронно в момент клика. Если QUEUE_CONNECTION в .env выставлен в database или redis, но воркер очереди не запущен — задачи просто накапливаются и никогда не исполняются:

php artisan queue:work --tries=3 --timeout=90

В продакшене это должен держать supervisor или systemd-юнит с autorestart=true, а не запуск руками в терминале, который умрёт при отключении SSH-сессии.

Если используете Redis как драйвер очереди и он сам иногда залипает — см. частые ошибки Redis на сервере.

PDF не генерируется или ломается вёрстка

Счета и котировки в Invoice Ninja рендерятся в PDF через headless-браузер (snappdf на базе Chromium в актуальных версиях). Это одно из самых требовательных к окружению мест приложения, и здесь чаще всего вылезают ошибки уровня системных библиотек, а не самого PHP-кода.

Типичная картина: кнопка «Скачать PDF» крутится и падает таймаутом, либо в логе — Failed to launch chrome или libnss3.so: cannot open shared object file. Если Invoice Ninja стоит вручную на голом сервере (не через официальный Docker-образ), headless Chromium требует системные библиотеки, которых на минимальном Ubuntu/Debian нет из коробки:

sudo apt install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
  libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 \
  libxrandr2 libgbm1 libasound2 libpango-1.0-0 libpangocairo-1.0-0

Дальше — права и память. Snappdf кэширует свою копию Chromium в storage/app, и если у PHP-FPM нет права на запись туда или не хватает памяти на запуск браузера — генерация падает без внятного сообщения на фронте, причину смотрите в storage/logs/laravel.log. На маленьком сервере (1-2 ГБ RAM) несколько одновременных генераций PDF могут вылетать по OOM — это не баг приложения, а нехватка ресурсов; либо добавляйте память, либо ограничивайте число параллельных queue-воркеров (numprocs=1), чтобы PDF генерировались по очереди.

Если PDF генерируется, но вёрстка «съезжает» — обычно в шаблоне счёта используется CSS-шрифт, которого нет в контейнере. Chromium рендерит локально и не всегда успевает подгрузить интернет-шрифт за таймаут — надёжнее встраивать шрифт как base64 в шаблон или использовать системные.

Письма клиентам не уходят

Счёт создан, статус «Sent», но клиент письма не получил — вторая по частоте боль после проблем с recurring invoices, и причины у неё почти всегда в SMTP-настройках, а не в Invoice Ninja.

Проверка начинается с .env:

MAIL_MAILER=smtp
MAIL_HOST=smtp.yourprovider.com
MAIL_PORT=587
MAIL_USERNAME=noreply@example.com
MAIL_PASSWORD=секрет
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@example.com

После изменения .env обязательно сбросьте кэш конфигурации — Laravel кэширует конфиг, и без этого шага изменения просто не подхватятся:

php artisan config:clear
php artisan cache:clear

Проверить отправку можно прямо из консоли, не создавая тестовый счёт в интерфейсе:

php artisan tinker
>>> Mail::raw('test', function($m) { $m->to('you@example.com')->subject('test'); });

Если письмо не уходит и в логе видна ошибка соединения — почти всегда это либо закрытый исходящий порт 587/465 у хостера (некоторые дешёвые VPS-тарифы режут исходящий SMTP из-за спама по умолчанию), либо провайдер требует верификации через SPF/DKIM. Отдельная ловушка — Gmail в качестве MAIL_HOST: обычный пароль от аккаунта уже не работает, нужен App Password из настроек безопасности.

Если письма уходят, но попадают в спам — проверьте SPF, DKIM и DMARC записи для домена в MAIL_FROM_ADDRESS. Без них Gmail и Яндекс сейчас массово помечают такие письма как подозрительные, даже если отправка технически прошла успешно.

Ошибки базы данных и деградация производительности со временем

Invoice Ninja на старте с MySQL работает без проблем, но по мере роста базы (сотни клиентов, тысячи счетов, вложения) начинают вылезать характерные ошибки.

Первая — SQLSTATE[HY000]: General error: 1153 Got a packet bigger than max_allowed_packet. Она возникает при импорте больших дампов или при прикреплении крупных файлов к счетам (сканы, PDF-приложения). Лечится увеличением лимита в MySQL:

# /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
max_allowed_packet = 256M
sudo systemctl restart mysql

Вторая — таймауты и обрывы соединения при миграциях (php artisan migrate) на слабом VPS с 1 ГБ RAM: MySQL и PHP вместе с headless Chromium для PDF не помещаются в память одновременно, и OOM-killer ядра убивает один из процессов. Если в dmesg видны строки Out of memory: Killed process рядом с mysqld или php-fpm — это не ошибка конфигурации, а нехватка ресурсов, и подкачка временно спасает ситуацию:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

Общие проблемы MySQL — права доступа, too many connections, кодировки — разобраны в статье частые ошибки MySQL на сервере. Для базы Invoice Ninja отдельно стоит настроить регулярный бэкап: при self-hosted биллинге потеря базы означает потерю истории всех выставленных счетов — команды и грабли собраны в статье про бэкап MySQL на сервере.

Третья типичная проблема — очередь перестаёт справляться именно с ростом базы: если задач накопилось много, а воркер настроен без ограничения по времени, один «зависший» PDF блокирует всю очередь на неопределённый срок. Разумно сразу выставлять явный таймаут в queue:work и настраивать supervisor на автоперезапуск воркера при падении.

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

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

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

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

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

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

Invoice Ninja работает на VPS с 1 ГБ RAM?

Запустится, но headless Chromium для PDF и MySQL вместе часто упираются в память при одновременной нагрузке. Разумный минимум — 2 ГБ RAM, лучше 4 ГБ, если параллельно гоняете несколько воркеров очереди.

Ставить через Docker или вручную?

Вручную можно, но системные зависимости для PDF и настройку cron/supervisor придётся собирать самостоятельно. Docker-образ избавляет от большей части этой возни ценой небольшого расхода ресурсов на контейнер.

Почему после обновления всё сломалось?

Чаще всего забыли php artisan migrate --force для новых миграций и php artisan config:clear для сброса устаревшего кэша. При Docker-обновлении проверьте, что volume со storage и базой не потерялся при пересоздании контейнеров.

Можно использовать SQLite вместо MySQL?

Технически да, но для продакшена с несколькими пользователями и очередью задач MySQL/MariaDB надёжнее — SQLite хуже переносит конкурентную запись воркера параллельно с веб-интерфейсом.

Интерфейс пишет только «Something went wrong», что делать?

Смотрите storage/logs/laravel.log — интерфейс намеренно скрывает детали от пользователя, но в лог пишется полный стектрейс с конкретной причиной.

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

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

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