Invoice Ninja на сервере: частые ошибки и решения
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 ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →