И снова добро пожаловать!
Прежде чем переходить к серверу, коротко разберемся, что вообще будем разворачивать.
Laravel — это PHP-фреймворк для разработки веб-приложений. Он предоставляет готовую структуру проекта и инструменты для маршрутизации, работы с базой данных, авторизации, очередей, фоновых задач, миграций, кеширования и других типичных задач backend-разработки.
Проще говоря, вместо того чтобы каждый раз собирать веб-приложение на PHP с нуля, разработчик получает готовый каркас и набор компонентов, вокруг которых уже строится логика проекта. На Laravel создают API, административные панели, интернет-магазины, внутренние сервисы и другие веб-приложения.
Но сам Laravel не является полноценным production-окружением. Код приложения нужно где-то исполнять, запросы пользователей — принимать, данные — хранить, фоновые задачи — обрабатывать, а запланированные команды — запускать по расписанию. Поэтому на VPS вокруг Laravel появляется несколько отдельных компонентов, каждый из которых берет на себя свою часть работы.
Именно эту схему мы сегодня и соберем: Nginx будет принимать HTTP- и HTTPS-запросы, PHP-FPM — исполнять PHP-код, PostgreSQL — хранить данные, queue worker — обрабатывать фоновые задачи, а scheduler — запускать задания по расписанию.
Если хотя бы один из этих уровней настроен неправильно, приложение может формально находиться на сервере, но при этом отдавать 403 Forbidden, 502 Bad Gateway, терять фоновые задания или ломаться после перезагрузки.
В этой статье мы пройдем полный путь развертывания небольшого Laravel-приложения на VPS: подготовим сервер, установим PHP-FPM и PostgreSQL, разместим проект, настроим Nginx, очереди, scheduler и HTTPS. Затем проверим, что всё автоматически поднимается после перезагрузки, и отдельно разберем типовые проблемы с правами доступа, ошибками 403 и 502.
Для облегчения восприятия сначала разберем, что происходит и зачем нужен каждый компонент, а команды и конфигурации соберем в практических блоках в конце соответствующих глав. Как говорится — теория и практика.
Как будет устроено развертывание Laravel
Прежде чем переходить к установке, полезно увидеть всю схему целиком. Чтобы плюс-минус понимать что к чему относится.
Для небольшого Laravel-приложения на одном VPS архитектура может выглядеть так:

Параллельно с основным веб-приложением работают еще два процесса:
Laravel Queue Worker
→ фоновые задания
Laravel Scheduler
→ задачи по расписанию
Все эти компоненты находятся на одном VPS, но выполняют разные функции. Поэтому дальше будем настраивать их отдельно и постепенно соединять в одну систему.
Какие компоненты участвуют в схеме
Nginx становится внешней точкой входа.
Он принимает запросы пользователей, обслуживает статические файлы и передает PHP-запросы в PHP-FPM.
PHP-FPM уже запускает PHP-код Laravel и возвращает результат обратно Nginx.
Сам Laravel отвечает за бизнес-логику приложения: маршруты, контроллеры, модели, работу с базой данных, очередями и другими сервисами.
PostgreSQL хранит постоянные данные приложения.
Если Laravel выполняет тяжелые или отложенные задачи — например, отправляет письма, формирует отчеты или обрабатывает загруженные файлы, — такие операции лучше передавать в очередь.
Их выполняет отдельный queue worker.
Scheduler решает другую задачу: запускает периодические действия по расписанию. Например, очистку старых записей, ежедневные отчеты или служебные операции.
В итоге приложение оказывается не одним процессом, а небольшой системой:

И именно поэтому полноценное развертывание требует больше шагов, чем обычное копирование PHP-файлов.
Почему Laravel нельзя просто «залить на сервер»
Этот вопрос, наверное, интересует многих. Почему не всё так просто?
Дело в том, что Laravel зависит от окружения.
Сам код проекта не содержит работающий PHP-интерпретатор, PostgreSQL, Nginx или Composer.
Кроме того, приложению нужны:
- PHP нужной версии;
- PHP-расширения;
- зависимости Composer;
- файл
.env; - ключ приложения;
- подключение к базе данных;
- writable-каталоги;
- настроенный веб-сервер;
- отдельный процесс для очередей;
- механизм запуска scheduler.
Даже если просто скопировать проект в /var/www, это еще не означает, что приложение сможет открыться в браузере.
Например, Nginx должен отдавать наружу только каталог public, PHP-FPM должен иметь доступ к нужным файлам, а Laravel — возможность записывать данные в storage и bootstrap/cache.
Поэтому правильное развертывание — это последовательная настройка всех уровней, а не одна операция загрузки проекта.
Какие ресурсы VPS подойдут для небольшого приложения
Требования сильно зависят от самого приложения, нагрузки и количества фоновых задач.
Но для небольшого проекта можно использовать достаточно скромный VPS.
Для совсем легкого приложения минимальным вариантом будет примерно 1 vCPU и 1–2 ГБ RAM. В нашей работе будем использовать именно этот набор.
Такой сервер подойдет для небольшой нагрузки, но запаса будет немного: Nginx, PHP-FPM, PostgreSQL и queue worker должны делить одну память.
Более комфортная стартовая конфигурация — 2 vCPU и 2–4 ГБ RAM.
Она дает больше пространства для PHP-FPM, PostgreSQL и фоновых процессов и лучше переносит кратковременные пики нагрузки.
Для диска желательно использовать SSD-хранилище, потому что база данных и Composer заметно чувствительны к скорости операций ввода-вывода.
Если проект активно обрабатывает изображения, запускает несколько workers или выполняет тяжелые запросы к PostgreSQL, ресурсы стоит увеличивать уже по фактической нагрузке.
Какие версии будем использовать
Чтобы инструкция была воспроизводимой, зафиксируем конкретный стек. При этом это не единственная рабочая комбинация: Laravel, PHP и PostgreSQL поддерживают несколько соседних версий, поэтому в реальном проекте в первую очередь нужно смотреть требования самого приложения.
Для этой статьи используем:
- Ubuntu 26.04 LTS;
- Laravel 13;
- PHP 8.4;
- PostgreSQL 18;
- Nginx из репозитория Ubuntu;
- Composer 2.x.
Ниже — краткая таблица совместимости и возможных альтернатив для каждого компонента:
| Компонент | Используем в статье | Какие версии также подойдут | На что обратить внимание |
| Ubuntu | 26.04 LTS | 24.04 LTS | Названия и версии пакетов могут отличаться |
| Laravel | 13.x | Более ранняя поддерживаемая версия проекта | Проверять composer.json и требования приложения |
| PHP | 8.4 | 8.3, 8.5 | Laravel 13 требует PHP ≥ 8.3; расширения тоже должны быть совместимы |
| PostgreSQL | 18 | 14–17 | Для Laravel важнее наличие драйвера pdo_pgsql, чем конкретная major-версия |
| Nginx | Версия из репозитория Ubuntu | Другая актуальная поддерживаемая версия | Синтаксис базовой Laravel-конфигурации обычно не меняется |
| Composer | 2.x | Актуальный релиз ветки 2.x | Версия должна поддерживать PHP и зависимости проекта |
Этого достаточно, чтобы зафиксировать рабочее окружение и при этом оставить пространство для проекта, который использует соседние версии компонентов.
Что подготовить перед развертыванием
До установки компонентов лучше заранее собрать все исходные данные в одну объемную, удобную таблицу:
| Компонент | Зачем нужен | Что должно быть готово |
| VPS | Размещение приложения | Ubuntu 26.04 LTS, SSH-доступ, минимум 1–2 ГБ RAM |
| Laravel | Само веб-приложение | Готовый проект и доступ к его исходному коду |
| PHP-FPM | Выполнение PHP-кода | Установим PHP 8.4 и нужные расширения |
| Composer | Установка PHP-зависимостей | Composer 2.x |
| PostgreSQL | Основная база данных | Создадим отдельную базу и пользователя |
| Nginx | Прием HTTP/HTTPS-запросов | Настроим отдельный server block |
| Домен | Публичный адрес приложения | DNS-запись должна управляться пользователем |
| Queue worker | Обработка фоновых задач | Настроим отдельный systemd service |
| Scheduler | Запуск задач Laravel по расписанию | Настроим через cron |
| TLS | HTTPS | Выпустим сертификат после проверки HTTP |
Думаю, что теперь понятно, почему нельзя просто развернуть Laravel на VPS. Впереди нас ждёт довольно долгий путь.
Подготовленный читатель может спросить - а почему бы не использовать докер, там же всё намного проще. И будет прав, но мы осознанно распишем углублённый материал, который поможет понять внутреннюю механику компонентов приложения, как они работают и что используют.
Итак, следующий этап — подготовить сам VPS: обновить систему, установить базовые пакеты и проверить версии компонентов, на которых дальше будет работать Laravel.
Подготавливаем VPS
Почему лучше начинать с чистой системы
Для первого развертывания удобнее использовать новый VPS без ранее настроенных веб-серверов, альтернативных PHP-репозиториев и старых конфигураций приложений.
Так проще понять, откуда появляется каждый компонент и какие настройки влияют на Laravel.
На давно используемом сервере могут уже существовать:
- Другая версия PHP;
- Старый PHP-FPM pool;
- Несколько конфигураций Nginx;
- Альтернативные PostgreSQL-репозитории;
- Пользовательские firewall rules;
- Измененные права на
/var/www; - Сторонние systemd services.
Если вы новичок, то настоятельно рекомендуем развернуть Laravel на чистом сервере. Если же собираетесь ставить на уже используемом, то перед установкой стоит проверить уже работающие сервисы и убедиться, что новые пакеты и порты не конфликтуют с ними.
Какие пакеты нужны Laravel
Laravel не устанавливается как один системный пакет. Серверу потребуется несколько групп программ.
Для работы приложения нужны PHP и PHP-FPM. Сам Laravel выполняется PHP-интерпретатором, а PHP-FPM принимает запросы от Nginx и запускает соответствующий PHP-код.
Отдельно понадобятся PHP-расширения. Их полный набор зависит от проекта, но типичное Laravel-приложение использует расширения для PostgreSQL, строк UTF-8, XML, HTTP-запросов, архивов и других стандартных операций. Подробно их разберем в следующей главе.
Для веб-доступа нужен Nginx, а постоянные данные будем хранить в PostgreSQL.
Composer понадобится для установки PHP-зависимостей, описанных в composer.json.
Кроме того, полезно сразу иметь несколько базовых системных инструментов:
- git — для получения и обновления исходного кода;
- curl — для загрузки файлов и проверки HTTP-запросов;
- unzip — используется некоторыми пакетами Composer;
- ca-certificates — для проверки TLS-сертификатов удаленных сервисов;
- tzdata — для системной информации о часовых поясах.
Некоторые из этих пакетов уже могут присутствовать в минимальном образе VPS. Повторная установка через пакетный менеджер в таком случае просто подтвердит их наличие.
После того как базовый набор программ определен, стоит проверить еще несколько системных настроек, от которых зависит стабильная работа приложения и удобство администрирования.
Почему важно настроить время, hostname и базовую безопасность
Корректное системное время нужно сразу нескольким частям приложения: логам, PostgreSQL, очередям, scheduler, TLS-сертификатам и временным токенам.
На Ubuntu синхронизация времени обычно уже включена, поэтому здесь достаточно проверить текущие дату, время и часовой пояс. Для сервера часто удобно использовать UTC, а нужный часовой пояс задавать уже на уровне Laravel.
Hostname на работу приложения почти не влияет, но сильно упрощает администрирование. Если сервер называется laravel-prod-01, его проще отличить от тестового или резервного VPS, чем по случайному имени от провайдера.
Заодно стоит проверить базовые настройки доступа:
- Работать через отдельного пользователя с sudo;
- Использовать SSH-ключи;
- Не открывать наружу лишние порты;
- Не публиковать PostgreSQL в Интернет, если база используется только локальным Laravel-приложением.
На этом этапе глубокий hardening не нужен. Нам достаточно убедиться, что сервер правильно показывает время, имеет понятный hostname и не выставляет наружу сервисы, которым внешний доступ не требуется.
Команды подготовки сервера

Сначала обновим информацию о пакетах и установленные компоненты:
sudo apt update
sudo apt upgrade -yУстановим базовые системные утилиты:
sudo apt install -y \
ca-certificates \
curl \
git \
unzip \
tzdataТеперь установим основные компоненты будущего стека:
sudo apt install -y \
nginx \
php \
php-fpm \
postgresql \
postgresql-contrib \
composerВ Ubuntu 26.04 метапакет php-fpm устанавливает системную ветку PHP 8.5, а стандартный PostgreSQL соответствует ветке 18. Если проект требует PHP 8.4 или 8.3, такую версию тоже можно использовать, но способ ее установки уже будет зависеть от выбранного репозитория и политики обновлений сервера.
Проверим основные версии:
php -v
php-fpm8.5 -v
nginx -v
psql --version
composer --versionУбедимся, что основные сервисы запущены:
sudo systemctl status nginx
sudo systemctl status php8.5-fpm
sudo systemctl status postgresqlДля краткой проверки без подробного вывода можно использовать:
systemctl is-active nginx php8.5-fpm postgresqlЕсли все три сервиса работают, команда должна вернуть для каждого состояние active.
Отдельно проверим hostname:
hostnamectlПри необходимости зададим более понятное имя сервера:
sudo hostnamectl set-hostname laravel-prod-01Состояние системного времени можно посмотреть так:
timedatectlЕсли требуется изменить часовой пояс, сначала можно посмотреть доступные варианты:
timedatectl list-timezonesА затем установить нужный, например UTC:
sudo timedatectl set-timezone UTCДля серверов UTC часто удобен тем, что делает логи и расписания предсказуемыми независимо от физического расположения VPS. Сам Laravel при необходимости может использовать собственный часовой пояс приложения.
Основные команды подготовки VPS
| Задача | Команда |
| Обновить индекс пакетов | sudo apt update |
| Установить обновления | sudo apt upgrade -y |
| Установить базовые утилиты | sudo apt install -y ca-certificates curl git unzip tzdata |
| Установить основной стек | sudo apt install -y nginx php php-fpm postgresql postgresql-contrib composer |
| Проверить PHP | php -v |
| Проверить PHP-FPM | php-fpm8.5 -v |
| Проверить Nginx | nginx -v |
| Проверить PostgreSQL | psql --version |
| Проверить Composer | composer --version |
| Проверить сервисы | systemctl is-active nginx php8.5-fpm postgresql |
| Проверить hostname | hostnamectl |
| Проверить время | timedatectl |
Базовое окружение готово.
Устанавливаем PHP-FPM и расширения Laravel
После базовой подготовки VPS можно переходить к PHP. Для Laravel здесь важен не только сам интерпретатор, но и PHP-FPM — именно через него Nginx будет передавать приложению PHP-запросы.
Кроме того, Laravel и его зависимости используют набор PHP-расширений. Поэтому на этом этапе нужно собрать полноценное PHP-окружение и сразу проверить, что оно подходит проекту.
Как Laravel работает через PHP-FPM
Nginx умеет принимать HTTP-запросы, отдавать статические файлы и проксировать трафик, но сам PHP-код он не исполняет.
Когда пользователь открывает страницу Laravel, запрос проходит примерно по такой цепочке:

Nginx определяет, что запрос нужно передать PHP, и отправляет его в PHP-FPM через FastCGI.
PHP-FPM принимает запрос, запускает PHP-код приложения и возвращает результат обратно Nginx. После этого Nginx уже отправляет готовый HTTP-ответ пользователю.
Связь между Nginx и PHP-FPM обычно строится через Unix socket, например:
/run/php/php8.5-fpm.sockТакже можно использовать TCP-соединение, например 127.0.0.1:9000, но для PHP-FPM и Nginx на одном VPS Unix socket обычно проще и не требует открытия дополнительного сетевого порта.
При этом сам Laravel не должен знать, работает PHP-FPM через socket или TCP. Для приложения это просто среда, в которой выполняется PHP-код.
Теперь можно перейти к следующему вопросу: одного PHP-интерпретатора для Laravel недостаточно — проекту нужны дополнительные расширения.
Какие PHP-расширения обычно нужны
Набор расширений зависит от конкретного Laravel-приложения и используемых Composer-пакетов.
Для типичного проекта с PostgreSQL обычно понадобятся:
pdo_pgsql— подключение к PostgreSQL через PDO;- mbstring — работа с многобайтовыми строками;
- xml — обработка XML и зависимости, использующие DOM;
- curl — HTTP-запросы к внешним сервисам;
- zip — работа с ZIP-архивами;
- bcmath — точные арифметические операции;
- intl — локализация, форматирование дат, чисел и строк;
- ctype — проверка типов символов;
- fileinfo — определение типов файлов.
Часть расширений может уже входить в базовую поставку PHP или устанавливаться вместе с другими пакетами.
Точный набор лучше определять по требованиям проекта. Composer обычно сразу сообщает, если приложению не хватает конкретного расширения, например ext-intl или ext-pdo_pgsql.
Поэтому лучше не устанавливать вообще все доступные PHP-модули чтоб «наверняка», а поставить базовый набор и дополнить его теми расширениями, которые действительно требуют приложение и его зависимости.
После установки модулей остается проверить еще один уровень — настройки самого PHP-FPM.
Что важно проверить в php.ini и пуле PHP-FPM
Основные настройки PHP находятся в php.ini, но для веб-приложения важно учитывать именно конфигурацию PHP-FPM.
У CLI и PHP-FPM могут быть разные php.ini. Поэтому ситуация, когда php -i показывает одно значение, а приложение через Nginx работает с другим, вполне возможна.
Для PHP 8.5 путь к конфигурации FPM обычно выглядит примерно так:
/etc/php/8.5/fpm/php.iniОтдельно существуют конфигурации пулов PHP-FPM, например:
/etc/php/8.5/fpm/pool.d/www.confПул определяет, от какого пользователя запускаются worker-процессы, где находится socket и как PHP-FPM управляет дочерними процессами.
Для стандартной установки Ubuntu чаще всего используется пользователь www-data. Этот момент позже станет особенно важным при настройке прав на storage и bootstrap/cache.
В php.ini обычно стоит проверить несколько параметров:
memory_limit— максимальный объем памяти для одного PHP-процесса;upload_max_filesize— максимальный размер загружаемого файла;post_max_size— максимальный размер POST-запроса;max_execution_time— максимальное время выполнения web-запроса;date.timezone— часовой пояс PHP, если он задается на этом уровне.
Но необязательно менять все значения сразу.
Для небольшого Laravel-приложения стандартных настроек часто достаточно. Менять лимиты имеет смысл под реальные требования проекта — например, если приложение принимает крупные файлы или выполняет тяжелые операции.
Также важно проверить, где PHP-FPM слушает запросы от Nginx. В пуле это определяется параметром listen.
Например:
listen = /run/php/php8.5-fpm.sockПозже этот же путь нужно будет указать в конфигурации Nginx. Если Nginx смотрит на другой socket, обычно появляется 502 Bad Gateway.
Таким образом, до настройки веб-сервера нужно убедиться в трех вещах: нужные расширения установлены, PHP-FPM запускается, а его socket известен.
Команды установки и проверки PHP-FPM
Если PHP и PHP-FPM уже были установлены на предыдущем этапе, остается добавить расширения, необходимые Laravel и PostgreSQL:
sudo apt install -y \
php-pgsql \
php-mbstring \
php-xml \
php-curl \
php-zip \
php-bcmath \
php-intlДля проверки установленной версии PHP:
php -vПосмотрим загруженные расширения:
php -mПри необходимости можно проверить конкретные модули:
php -m | grep -E 'pdo_pgsql|mbstring|xml|curl|zip|bcmath|intl'Проверим состояние PHP-FPM:
sudo systemctl status php8.5-fpmДля более короткой проверки:
systemctl is-active php8.5-fpmТеперь убедимся, что socket существует:
ls -l /run/php/php8.5-fpm.sockТекущую конфигурацию PHP CLI можно узнать так:
php --iniА путь к конфигурации PHP-FPM удобнее проверить отдельно:
php-fpm8.5 -i | grep "Loaded Configuration File"После изменения php.ini или конфигурации пула PHP-FPM сервис нужно перечитать:
sudo systemctl restart php8.5-fpmДалее у нас как обычно идёт удобная таблица. Позволит немного структурировать всё сказанное ранее.
Основные команды PHP-FPM
| Задача | Зачем | Команда |
| Установить основные расширения Laravel | Добавить поддержку PostgreSQL, строк, XML, HTTP-запросов, ZIP и других возможностей | sudo apt install -y php-pgsql php-mbstring php-xml php-curl php-zip php-bcmath php-intl |
| Проверить версию PHP | Убедиться, что используется нужная ветка PHP | php -v |
| Посмотреть загруженные модули | Проверить, какие расширения доступны PHP | php -m |
| Проверить нужные расширения | Быстро убедиться, что основные модули Laravel установлены | php -m | grep -E 'pdo_pgsql|mbstring|xml|curl|zip|bcmath|intl' |
| Проверить PHP-FPM | Убедиться, что сервис запущен | systemctl is-active php8.5-fpm |
| Проверить socket | Узнать, доступен ли socket для подключения Nginx | ls -l /run/php/php8.5-fpm.sock |
Посмотреть CLI php.ini | Узнать, какой конфигурационный файл использует PHP в терминале | php --ini |
Посмотреть FPM php.ini | Проверить конфигурацию, которую использует PHP-FPM | php-fpm8.5 -i | grep "Loaded Configuration File" |
| Перезапустить PHP-FPM | Применить изменения конфигурации | sudo systemctl restart php8.5-fpm |
Теперь PHP-окружение готово для выполнения Laravel-кода. Следующим шагом настроим PostgreSQL: создадим отдельную базу, пользователя приложения и подготовим параметры подключения для .env.
Настраиваем PostgreSQL для Laravel
Как Laravel подключается к PostgreSQL
Laravel не работает с PostgreSQL напрямую через собственный протокол. Между приложением и базой находится PHP-драйвер pdo_pgsql, который мы установили на предыдущем этапе.
Чтобы было понятнее, цепочка выглядит так:

Само приложение получает параметры подключения из конфигурации Laravel. В production они обычно задаются через файл .env, а затем используются настройками из config/database.php.
Для PostgreSQL Laravel должен знать:
- Адрес сервера базы данных;
- Порт;
- Название базы;
- Имя пользователя;
- Пароль.
Если PostgreSQL расположен на том же VPS, соединение можно оставить локальным. Это проще и безопаснее, чем разрешать внешние подключения к базе без необходимости.
Теперь стоит отделить само приложение от административной учетной записи PostgreSQL.
Почему лучше создать отдельного пользователя и базу
После установки PostgreSQL уже существует системная административная роль postgres. Использовать ее непосредственно в Laravel не стоит.
Приложению лучше выделить отдельную роль, например laravel_user, и отдельную базу:
laravel_dbТогда Laravel получает доступ только к своей базе, а административная учетная запись остается для обслуживания PostgreSQL.
Такое разделение полезно сразу по нескольким причинам:
- Приложение не работает с правами администратора PostgreSQL;
- Проще ограничивать разрешения;
- Учетные данные Laravel можно менять отдельно;
- Несколько приложений на одном сервере можно изолировать друг от друга;
- Ошибка или уязвимость одного проекта не дает ему автоматически доступ ко всем остальным базам.
Для небольшого приложения отдельному пользователю обычно достаточно владеть своей базой и создавать в ней объекты, необходимые миграциям Laravel.
После создания базы остается связать эти параметры с конфигурацией самого приложения.
Что важно в DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME
Параметры подключения Laravel обычно находятся в .env.
Для нашей схемы они будут выглядеть примерно так:
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=laravel_db
DB_USERNAME=laravel_user
DB_PASSWORD=strong_passwordDB_CONNECTION=pgsql говорит Laravel использовать PostgreSQL.
DB_HOST определяет адрес сервера базы. Поскольку PostgreSQL работает на том же VPS, здесь удобно использовать 127.0.0.1.
Это важный нюанс: localhost и 127.0.0.1 не всегда означают абсолютно одинаковый способ подключения. В некоторых клиентах localhost может приводить к использованию Unix socket, тогда как 127.0.0.1 явно задает TCP-соединение.
Для нашей инструкции используем 127.0.0.1, чтобы поведение было более очевидным:
Laravel → 127.0.0.1:5432 → PostgreSQL
DB_PORT — порт PostgreSQL. По умолчанию это 5432.
DB_DATABASE содержит название базы приложения, а DB_USERNAME — роль, от имени которой Laravel будет подключаться к ней.
Пароль задается через DB_PASSWORD. Его не стоит использовать повторно для SSH, панели провайдера или других сервисов.
Важно. Сам .env также нельзя добавлять в публичный Git-репозиторий: кроме учетных данных PostgreSQL, позже там будут находиться и другие секреты приложения.
Когда параметры определены, можно перейти к практической части и создать базу.
Команды создания базы и пользователя

Перейдем в PostgreSQL от имени системного пользователя postgres:
sudo -u postgres psqlСоздадим отдельную роль Laravel с паролем:
CREATE USER laravel_user WITH PASSWORD 'replace_with_strong_password';Теперь создадим базу и сразу назначим laravel_user ее владельцем:
CREATE DATABASE laravel_db
OWNER laravel_user;Проверим роли:
\duИ список баз:
\lПосле этого выйдем из psql:
\qТеперь проверим соединение уже не через административную учетную запись, а так, как к PostgreSQL будет обращаться приложение:
psql -h 127.0.0.1 -p 5432 -U laravel_user -d laravel_dbPostgreSQL запросит пароль laravel_user.
Если соединение установлено, можно дополнительно проверить текущую базу и пользователя:
SELECT current_database(), current_user;Ожидаемый результат должен соответствовать созданным значениям:
| current_database | current_user |
laravel_db | laravel_user |
После проверки выходим командой \q.
Проверка PostgreSQL
| Задача | Зачем | Команда |
| Открыть PostgreSQL от имени администратора | Создать роль и базу приложения | sudo -u postgres psql |
| Создать пользователя Laravel | Не использовать административную роль в приложении | CREATE USER laravel_user WITH PASSWORD '...'; |
| Создать базу | Выделить отдельное хранилище для приложения | CREATE DATABASE laravel_db OWNER laravel_user; |
| Посмотреть роли | Проверить создание пользователя | \du |
| Посмотреть базы | Проверить создание laravel_db | \l |
| Проверить подключение приложения | Убедиться, что credentials действительно работают | psql -h 127.0.0.1 -p 5432 -U laravel_user -d laravel_db |
| Проверить активную базу и пользователя | Исключить подключение не к той базе или роли | SELECT current_database(), current_user; |
Теперь PostgreSQL готов принимать подключения от приложения. Топаем дальше.
Размещаем Laravel-приложение на сервере
PHP и PostgreSQL уже готовы, поэтому теперь можно переходить к самому приложению. На этом этапе разместим код в отдельном каталоге, установим Composer-зависимости, подготовим .env и настроим права так, чтобы Laravel мог записывать данные, но при этом весь проект не был открыт на запись без необходимости.
Где лучше хранить код
Для веб-приложений на Linux обычно используют каталог /var/www.
Для нашего проекта можно создать отдельную директорию:
/var/www/laravel-appВнутри будет находиться весь Laravel-проект:

Отдельный каталог удобен тем, что не смешивает код приложения с конфигурациями Nginx, PostgreSQL или systemd.
Если на одном VPS позже появятся несколько проектов, структура может выглядеть так:

Сам код можно получить через Git, загрузить архивом или доставлять через CI/CD. В этой инструкции используем Git как наиболее понятный вариант.
Перед настройкой Nginx важно сразу разобраться, какой именно каталог приложения должен быть доступен из браузера.
Почему document root должен указывать на public
Laravel специально отделяет публичные файлы от внутренней части проекта.
Каталог public содержит точку входа приложения:
public/index.phpТам же обычно находятся CSS, JavaScript, изображения и другие ресурсы, которые можно отдавать пользователю напрямую.
Все остальное должно оставаться за пределами document root:
.envconfig/vendor/storage/database/
Поэтому Nginx позже должен использовать:
/var/www/laravel-app/publicа не:
/var/www/laravel-appЕсли указать корень всего проекта, веб-сервер потенциально сможет отдавать файлы, которые вообще не должны быть доступны извне.
То есть структура должна быть такой:

При этом Laravel из index.php уже самостоятельно обращается к внутренним каталогам проекта.
Следующий важный момент — не все части проекта должны иметь одинаковые права.
Если дать PHP-FPM право изменять весь каталог приложения, уязвимость в самом приложении потенциально позволит менять PHP-код, конфигурацию или другие файлы проекта. Если, наоборот, сделать права слишком строгими, Laravel не сможет записывать логи, cache и другие runtime-данные.
Поэтому права лучше разделять: код в основном остается доступным только для чтения, а запись разрешается лишь там, где она действительно нужна — прежде всего в storage и bootstrap/cache.
Как работают .env, storage и bootstrap/cache
Эти три части выполняют совершенно разные задачи.
.env хранит параметры окружения конкретного сервера. В нем находятся настройки базы данных, URL приложения, режим работы, драйвер очередей и другие параметры, которые не стоит жестко прописывать в исходном коде.
Например:
APP_ENV=production
APP_DEBUG=false
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=laravel_db
DB_USERNAME=laravel_user
DB_PASSWORD=replace_with_strong_passwordStorage используется уже во время работы приложения. Laravel хранит там:
- Логи;
- Cache-файлы;
- Сессии при файловом драйвере;
- Временные данные;
- Скомпилированные представления;
- Пользовательские файлы, если приложение настроено соответствующим образом.
Bootstrap/cache нужен Laravel для кэшированных конфигураций, маршрутов и других служебных файлов.
Именно storage и bootstrap/cache должны быть доступны на запись пользователю, от которого работает PHP-FPM.
Остальная часть проекта обычно не требует постоянного права записи со стороны веб-процесса.
Отсюда и возникает вопрос правильных Linux permissions.
Почему права доступа важнее, чем chmod 777
Одна из самых частых попыток быстро исправить Permission denied выглядит так:
chmod -R 777После этого проблема действительно иногда исчезает, потому что запись разрешается всем пользователям системы.
Но вместе с проблемой исчезает и нормальная модель доступа.
Для production лучше разделить две роли:
- Пользователь, который размещает и обновляет код;
- Пользователь PHP-FPM, который выполняет приложение.
В Ubuntu PHP-FPM обычно работает от www-data.
Ему не нужно давать право изменять весь Laravel-проект. Для нормальной работы достаточно доступа на запись к:
storage/bootstrap/cache/
Например, сам код может принадлежать административному пользователю, а каталоги, куда Laravel пишет во время работы, — группе www-data.
Такой подход позволяет решить проблему с правами точечно, а не открывать весь проект для записи.
Еще один важный момент: права на файл и права на каталог — не одно и то же. Чтобы PHP-FPM мог создать новый файл внутри storage, ему нужен доступ не только к самому файлу, но и к родительскому каталогу.
Поэтому проблемы с Laravel permissions лучше диагностировать по владельцу, группе и правам конкретного дерева каталогов, а не пытаться исправлять их одним глобальным chmod.
Теперь можно собрать всё это на практике.
Практика: размещаем проект и готовим Laravel

Перейдем в /var/www:
cd /var/wwwПолучим проект из Git-репозитория:
sudo git clone https://example.com/your-project.git laravel-appПередадим проект текущему пользователю, чтобы дальнейшие команды не приходилось выполнять от root:
sudo chown -R "$USER":"$(id -gn)" /var/www/laravel-appПерейдем в каталог приложения:
cd /var/www/laravel-appУстановим production-зависимости Composer:
composer install \
--no-dev \
--optimize-autoloader \
--no-interactionЕсли репозиторий содержит пример окружения .env.example, создадим рабочий .env:
cp .env.example .envТеперь откроем файл и укажем параметры нашего сервера:
nano .envИ ещё дополнительно проверим:
APP_ENV=production
APP_DEBUG=false
APP_URL=https://app.example.com
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=laravel_db
DB_USERNAME=laravel_user
DB_PASSWORD=replace_with_strong_passwordСгенерируем Laravel application key:
php artisan key:generateЕсли база пока пустая и приложение использует Laravel migrations, выполним:
php artisan migrate --forceПараметр --force нужен для выполнения migration в production-окружении без интерактивного подтверждения. Перед применением миграций к существующей production-базе их, разумеется, нужно предварительно проверить.
Теперь настроим права на рабочие каталоги Laravel.
Назначим им группу www-data:
sudo chgrp -R www-data storage bootstrap/cacheРазрешим владельцу и группе чтение, запись и доступ к каталогам:
sudo chmod -R ug+rwX storage bootstrap/cacheДля самого .env можно оставить более строгие права:
chmod 600 .envПроверим владельцев и разрешения:
ls -ld \
/var/www/laravel-app \
storage \
bootstrap/cacheИ отдельно .env:
ls -l .envПосле этого можно выполнить несколько базовых проверок Laravel:
php artisan aboutПроверим подключение к базе через Laravel:
php artisan migrate:statusЕсли приложение успешно загружается, видит PostgreSQL и не получает ошибок записи в storage, базовая подготовка проекта завершена.
Основные команды подготовки Laravel
| Задача | Зачем | Команда |
| Получить проект | Разместить исходный код на VPS | sudo git clone https://example.com/your-project.git laravel-app |
| Передать код текущему пользователю | Не работать с проектом постоянно через root | sudo chown -R "$USER":"$(id -gn)" /var/www/laravel-app |
| Установить зависимости | Подготовить PHP-библиотеки приложения | composer install --no-dev --optimize-autoloader --no-interaction |
Создать .env | Подготовить конфигурацию сервера | cp .env.example .env |
Сгенерировать APP_KEY | Создать ключ шифрования Laravel | php artisan key:generate |
| Выполнить миграции | Подготовить структуру PostgreSQL | php artisan migrate --force |
| Передать рабочие каталоги группе PHP-FPM | Разрешить Laravel записывать служебные файлы | sudo chgrp -R www-data storage bootstrap/cache |
| Выдать права на запись | Разрешить владельцу и www-data работать с runtime-каталогами | sudo chmod -R ug+rwX storage bootstrap/cache |
Ограничить .env | Защитить конфигурацию и credentials | chmod 600 .env |
| Проверить Laravel | Убедиться, что приложение запускается | php artisan about |
| Проверить базу | Убедиться, что Laravel подключается к PostgreSQL | php artisan migrate:status |
Дальше нас ждёт настройка.
Настраиваем Nginx для Laravel
Само приложение уже находится на сервере, зависимости установлены, а Laravel умеет обращаться к PostgreSQL. Но пока к нему нельзя нормально попасть из браузера: нужен веб-сервер, который примет запрос пользователя и передаст выполнение PHP-кода в PHP-FPM.
Эту роль будет выполнять Nginx. Здесь особенно важно правильно выбрать document root, настроить передачу PHP-запросов и не открыть наружу внутренние файлы проекта.
Как Nginx передает PHP-запросы в PHP-FPM
Nginx сам не исполняет PHP-код. Когда приходит запрос к Laravel, он определяет, нужно ли вернуть статический файл или передать обработку приложению.
Для PHP-запросов используется FastCGI. В нашей схеме Nginx обращается к Unix socket PHP-FPM:
/run/php/php8.5-fpm.sockВыглядит это так:

В конфигурации Nginx связь с PHP-FPM задается директивой fastcgi_pass.
Она отвечает на простой вопрос: куда Nginx должен отправить PHP-запрос после того, как решил не обрабатывать его сам.
В нашей схеме используется Unix socket:
fastcgi_pass unix:/run/php/php8.5-fpm.sock;То есть Nginx не запускает PHP напрямую, а передает запрос уже работающему процессу PHP-FPM через специальный локальный socket-файл.
Есть и другой вариант — передавать запросы по TCP, например на 127.0.0.1:9000. Но если Nginx и PHP-FPM работают на одном VPS, Unix socket обычно удобнее: не нужен отдельный сетевой порт, а соединение остается локальным.
Важно, чтобы путь в fastcgi_pass совпадал с реальным socket PHP-FPM.
Например, если PHP-FPM слушает:
/run/php/php8.5-fpm.sockа Nginx настроен на:
/run/php/php8.4-fpm.sockсоединиться с PHP-FPM он не сможет. В результате пользователь обычно увидит 502 Bad Gateway.
Кроме fastcgi_pass, в PHP-блоке Nginx есть еще несколько важных директив.
include fastcgi_params; подключает стандартный набор FastCGI-параметров, которые Nginx передает PHP-FPM: метод запроса, URI, query string и другие данные HTTP-запроса.
fastcgi_param SCRIPT_FILENAME сообщает PHP-FPM, какой именно PHP-файл нужно выполнить. Без правильного пути PHP-FPM может получить запрос, но не понять, какой скрипт запускать.
Например:
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;Для Laravel это в итоге приводит к запуску:
/var/www/laravel-app/public/index.phpfastcgi_param DOCUMENT_ROOT дополнительно передает PHP информацию о корневом каталоге сайта.
То есть внутри PHP-блока происходит примерно следующее:

Но даже при правильно настроенном PHP-FPM Nginx еще должен понимать, какой запрос считать PHP-запросом и какой файл запускать для URL вроде /products/25.
И здесь уже появляется главная особенность Laravel — почти все динамические запросы должны проходить через public/index.php.
Почему все запросы должны проходить через public/index.php
Laravel использует единый входной файл:
/var/www/laravel-app/public/index.phpПользователь может открыть /login, /profile, /api/users или другой маршрут, хотя физических PHP-файлов с такими именами в каталоге public нет.
Вместо этого запрос передается в index.php, после чего уже Laravel определяет подходящий маршрут и запускает нужный controller.
Например:

При этом существующие статические файлы — изображения, CSS или JavaScript — Nginx может отдавать напрямую, не запуская PHP.
Именно поэтому document root для сайта должен указывать на:
/var/www/laravel-app/publicТак наружу попадает только публичная часть проекта, а внутренняя структура Laravel остается за пределами веб-корня.
Чтобы совместить реальные файлы и виртуальные маршруты Laravel, Nginx использует try_files.
Что делает try_files
Директива try_files определяет, что делать с входящим URL.
Для Laravel обычно используется логика:
try_files $uri $uri/ /index.php?$query_string;Она читается примерно так:
- Сначала проверить, существует ли файл с таким путем.
- Затем проверить, существует ли соответствующий каталог.
- Если ничего не найдено — передать запрос в
index.php, сохранив параметры строки запроса.
Например, пользователь запрашивает:
/css/app.cssЕсли такой файл действительно существует в public/css, Nginx отдаст его напрямую.
А запрос:
/users/15не соответствует физическому файлу, поэтому отправится в:
/index.phpи дальше попадет в маршрутизатор Laravel.
Без такой схемы Nginx попытался бы найти настоящий файл /users/15 и вернул бы 404 Not Found, даже если маршрут существует в Laravel.
Таким образом, try_files связывает обычную файловую структуру Nginx с маршрутизацией приложения.
При этом важно не только правильно направлять разрешенные запросы, но и не допустить прямого доступа к служебным файлам проекта.
Какие каталоги нельзя отдавать напрямую
Если document root уже указывает на public, большая часть внутренних файлов Laravel автоматически остается вне доступной пользователю области.
Например:
/var/www/laravel-app/.env/var/www/laravel-app/config//var/www/laravel-app/vendor//var/www/laravel-app/database//var/www/laravel-app/storage/
Они не находятся внутри /public и поэтому не должны отдаваться Nginx как обычные файлы.
Это одна из причин, почему document root нельзя устанавливать на /var/www/laravel-app.
Особенно опасна публикация .env. В нем могут находиться:
- пароль PostgreSQL;
APP_KEY;- SMTP credentials;
- токены API;
- ключи сторонних сервисов;
- параметры очередей и cache.
Каталог vendor тоже не должен быть отдельной публичной директорией: в нем находится код Composer-зависимостей, который приложение подключает самостоятельно.
А содержимое storage зависит от назначения конкретного подкаталога. Например, Laravel может хранить там логи и приватные файлы, которые нельзя просто отдавать по URL.
Если приложению действительно нужны публично доступные загруженные файлы, Laravel обычно использует отдельный путь public/storage, связанный с соответствующей директорией через symbolic link. Внутренний storage целиком при этом публичным не становится.
Теперь можно собрать эти правила в полноценную конфигурацию Nginx.
Практика: создаем конфигурацию Nginx

Создадим отдельный server block:
sudo nano /etc/nginx/sites-available/laravel-appДля первого запуска пока используем HTTP. HTTPS настроим позже, когда убедимся, что само приложение открывается корректно.
Пример конфигурации:
server {
listen 80;
listen [::]:80;
server_name app.example.com;
root /var/www/laravel-app/public;
index index.php index.html;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico {
access_log off;
log_not_found off;
}
location = /robots.txt {
access_log off;
log_not_found off;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.5-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
}
location ~ /\.(?!well-known).* {
deny all;
}
access_log /var/log/nginx/laravel-app.access.log;
error_log /var/log/nginx/laravel-app.error.log;
}Здесь нужно заменить:
app.example.comна реальный домен приложения.
Строка:
root /var/www/laravel-app/public;оставляет document root внутри public.
А:
fastcgi_pass unix:/run/php/php8.5-fpm.sock;связывает Nginx с PHP-FPM.
Отдельное правило:
location ~ /\.(?!well-known).* {
deny all;
}запрещает прямой доступ к скрытым файлам и каталогам, но оставляет исключение для .well-known, которое позже понадобится при проверке домена и выпуске TLS-сертификата.
Теперь активируем конфигурацию через symbolic link:
sudo ln -s /etc/nginx/sites-available/laravel-app \
/etc/nginx/sites-enabled/laravel-appЕсли стандартный сайт Nginx больше не нужен, его можно отключить:
sudo rm -f /etc/nginx/sites-enabled/defaultПеред применением обязательно проверим синтаксис:
sudo nginx -tПри успешной проверке появятся сообщения примерно такого вида:
syntax is ok
test is successfulТолько после этого перечитаем конфигурацию:
sudo systemctl reload nginxПроверим состояние Nginx:
systemctl is-active nginxЕсли DNS домена уже указывает на VPS, приложение можно проверить через браузер или curl:
curl -I http://app.example.comДо настройки HTTPS нормальным результатом будет обычный HTTP-ответ приложения.
Если домен пока не настроен, сервер можно временно проверить по IP, добавив подходящий server_name или локальную запись в hosts. Однако для дальнейшей настройки TLS лучше уже использовать настоящий домен.
Основные команды настройки Nginx
| Задача | Зачем | Команда |
| Создать конфигурацию | Описать отдельный virtual host для Laravel | sudo nano /etc/nginx/sites-available/laravel-app |
| Активировать сайт | Подключить конфигурацию в Nginx | sudo ln -s /etc/nginx/sites-available/laravel-app /etc/nginx/sites-enabled/laravel-app |
| Отключить стандартный сайт | Не допустить конфликта с default-конфигурацией | sudo rm -f /etc/nginx/sites-enabled/default |
| Проверить синтаксис | Найти ошибку до применения конфигурации | sudo nginx -t |
| Перечитать Nginx | Применить изменения без полной остановки сервиса | sudo systemctl reload nginx |
| Проверить состояние | Убедиться, что Nginx работает | systemctl is-active nginx |
| Проверить HTTP | Убедиться, что домен отвечает через Nginx | curl -I http://app.example.com |
| Посмотреть ошибки сайта | Найти проблемы Nginx или PHP-FPM | sudo tail -f /var/log/nginx/laravel-app.error.log |
По сути мы уже на финишной прямой по установке Laravel. На этом этапе веб-цепочка уже собрана: Nginx принимает HTTP-запросы, передает динамическую обработку в PHP-FPM, а Laravel получает доступ к PostgreSQL.
Дальше стоит привести конфигурацию Laravel в порядок: проверить .env, отключить debug и подготовить кэши конфигурации, маршрутов и представлений.
Настраиваем переменные окружения и Laravel cache
Какие параметры .env критичны для production
Файл .env хранит настройки конкретного окружения. В нем обычно находятся параметры, которые отличаются между локальной разработкой, staging и production.
Для production в первую очередь стоит проверить такие значения:
APP_ENV=production
APP_DEBUG=false
APP_URL=https://app.example.comAPP_ENV определяет тип окружения. Для рабочего сервера обычно используется production.
APP_DEBUG управляет подробным выводом ошибок. На production его нужно отключать — подробнее об этом поговорим чуть ниже.
APP_URL задает основной URL приложения. Laravel и отдельные пакеты могут использовать его при генерации абсолютных ссылок, callback URL и других адресов.
Дальше идут параметры PostgreSQL:
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=laravel_db
DB_USERNAME=laravel_user
DB_PASSWORD=replace_with_strong_passwordЭти значения должны соответствовать базе и пользователю, которых мы создали ранее.
Если приложение использует очереди, важно также проверить драйвер:
QUEUE_CONNECTION=databaseКонкретное значение зависит от проекта. Помимо базы данных, Laravel может использовать Redis, Amazon SQS и другие backend. В нашей статье позже настроим сам queue worker, поэтому здесь главное — убедиться, что выбранный драйвер действительно существует и подготовлен.
То же относится к сессиям и cache. Например:
SESSION_DRIVER=database
CACHE_STORE=databaseили другой вариант, который использует конкретный проект.
Нельзя просто копировать такие значения вслепую. Если указать database, но не создать необходимые таблицы, приложение начнет получать ошибки уже во время работы.
Поэтому .env лучше воспринимать как описание того, какие внешние компоненты и драйверы должен использовать именно этот экземпляр Laravel.
После проверки параметров отдельно стоит остановиться на APP_DEBUG.
Почему APP_DEBUG=false обязателен
Во время разработки подробные сообщения об ошибках полезны. Laravel может показать stack trace, файл, строку, параметры запроса и другую техническую информацию, которая помогает быстро найти проблему.
На production это уже становится риском.
Если оставить:
APP_DEBUG=trueобычный пользователь при ошибке потенциально может увидеть сведения о внутреннем устройстве приложения.
Например:
- Пути файлов на сервере;
- Имена классов и методов;
- Части SQL-запросов;
- Конфигурационные значения;
- Структуру приложения;
- Сведения о подключенных пакетах.
Даже если пароль напрямую не показывается, такая информация значительно упрощает анализ приложения злоумышленнику.
Поэтому рабочая конфигурация должна содержать:
APP_ENV=production
APP_DEBUG=falseТогда пользователю возвращается обычная страница ошибки, а подробности остаются в логах Laravel.
Отключение debug не мешает диагностике: ошибки по-прежнему можно искать в storage/logs, Nginx journal и логах PHP-FPM.
После настройки .env возникает еще один важный момент — Laravel умеет кэшировать часть своей конфигурации, чтобы не пересобирать ее на каждый запрос.
Чем отличаются config cache, route cache и view cache
Laravel использует несколько разных типов кэша, и каждый решает свою задачу.
Config cache объединяет конфигурационные файлы приложения в оптимизированный кэш.
После его создания Laravel не должен заново загружать все файлы из config/ при каждом запросе.
Условно:

Поэтому после изменения переменных окружения старый config cache может продолжать содержать прежние значения.
Route cache сохраняет подготовленное описание маршрутов.
Если у приложения много routes, Laravel не нужно каждый раз заново собирать их из исходных файлов.
Схема примерно такая:

Этот кэш особенно полезен для production, где маршруты обычно не меняются между запросами.
View cache относится к Blade-шаблонам. Laravel компилирует Blade в обычный PHP-код, чтобы не обрабатывать шаблон с нуля при каждом обращении.
Получается:

Эти три механизма решают разные задачи, поэтому очистка одного кэша не обязательно затрагивает остальные.
Чтобы не запоминать каждую команду отдельно, Laravel также предоставляет общую оптимизацию приложения. Но даже в этом случае полезно понимать, что именно находится внутри кэша и почему после изменения .env или routes иногда продолжает использоваться старое состояние.
Когда кэш нужно очищать после изменений
Кэш Laravel удобен до тех пор, пока конфигурация приложения остается неизменной.
После изменения .env особенно важно обновить config cache. Иначе приложение может продолжать использовать ранее сохраненное значение.
Например, администратор меняет:
DB_HOST=127.0.0.1на другой сервер PostgreSQL, но Laravel продолжает подключаться по старому адресу.
Причина может быть не в .env, а в уже созданном config cache.
Аналогичная ситуация возникает с routes. Если маршруты были изменены после создания route cache, приложение может продолжать работать со старой таблицей маршрутов до обновления кэша.
View cache обычно менее критичен: Laravel умеет перекомпилировать шаблоны, но при диагностике отображения его также бывает полезно очистить.
Практически удобно придерживаться простой логики:
- Изменили
.envили файлыconfig/— обновить config cache; - Изменили routes — обновить route cache;
- Возникли странности с Blade — очистить view cache;
- После крупного deployment — очистить старые оптимизационные файлы и собрать их заново.
Особенно важно помнить об этом при автоматическом deployment. Если код обновился, а старый кэш остался, приложение может оказаться в промежуточном состоянии: часть данных уже новая, а часть конфигурации еще относится к предыдущему релизу.
Теперь можно применить production-настройки и собрать кэш.
Команды настройки и кэширования Laravel
Сначала убедимся, что Laravel действительно работает в production-режиме:
php artisan aboutПосле изменения .env очистим ранее сохраненную конфигурацию:
php artisan config:clearЗатем создадим config cache заново:
php artisan config:cacheМаршруты можно кэшировать отдельно:
php artisan route:cacheА Blade-представления заранее скомпилировать:
php artisan view:cacheЕсли нужно удалить только route cache:
php artisan route:clearДля представлений:
php artisan view:clearЕсли требуется удалить основные оптимизационные кэши приложения сразу, удобно использовать:
php artisan optimize:clearПосле этого production-кэши можно собрать общей командой:
php artisan optimizeПеред использованием route cache стоит убедиться, что маршруты приложения совместимы с этим механизмом. Если команда завершается ошибкой, сначала нужно устранить указанную Laravel причину, а не игнорировать результат.
Основные команды Laravel cache
| Задача | Зачем | Команда |
| Проверить окружение Laravel | Убедиться, что приложение использует ожидаемую конфигурацию | php artisan about |
| Очистить config cache | Удалить сохраненные значения старой конфигурации | php artisan config:clear |
| Создать config cache | Ускорить загрузку production-конфигурации | php artisan config:cache |
| Создать route cache | Сохранить подготовленную таблицу маршрутов | php artisan route:cache |
| Очистить route cache | Применить измененные маршруты | php artisan route:clear |
| Создать view cache | Заранее скомпилировать Blade-шаблоны | php artisan view:cache |
| Очистить view cache | Удалить скомпилированные представления | php artisan view:clear |
| Очистить оптимизационные кэши | Удалить старые кэшированные данные перед deployment или диагностикой | php artisan optimize:clear |
| Оптимизировать приложение | Собрать production-кэши одной командой | php artisan optimize |
После этого production-конфигурация Laravel приведена в порядок: debug отключен, значения .env проверены, а конфигурация и другие часто используемые данные подготовлены для постоянной работы.
Однако HTTP-запросами приложение не ограничивается. Следующий слой — фоновые задачи, которые должны выполняться даже тогда, когда пользователь уже получил ответ. Для этого настроим Laravel queue worker и сделаем его отдельным постоянно работающим сервисом.
Запускаем queue worker
Зачем Laravel нужны очереди
Очередь позволяет отделить медленную или необязательную для текущего запроса работу от ответа пользователю.
Например, после регистрации приложение может:
- сохранить пользователя в базе;
- сразу вернуть успешный ответ;
- отдельно поставить отправку приветственного письма в очередь.
Тогда основная цепочка выглядит так:

А уже независимо от нее:

В очередь удобно выносить отправку почты, уведомления, генерацию отчетов, обработку изображений, импорт данных и другие операции, которые не обязательно завершать до формирования HTTP-ответа.
Это не только ускоряет интерфейс. Если внешний сервис временно недоступен, Laravel может повторить выполнение задания позже, вместо того чтобы сразу завершить пользовательский запрос ошибкой.
Однако сама очередь — это только место, где хранятся задания. Чтобы они действительно выполнялись, нужен отдельный процесс, который постоянно забирает Job из очереди и запускает их.
Для быстрой проверки такой worker можно запустить вручную из терминала. Но для постоянной работы этого недостаточно: процесс не должен зависеть от открытой SSH-сессии администратора.
Почему queue worker нельзя запускать просто в SSH
Для теста worker можно запустить вручную из каталога проекта.
Проблема в том, что такой процесс привязан к текущей shell-сессии.
Если закрыть SSH-подключение, завершить терминал или перезагрузить сервер, worker остановится. Новые задания продолжат попадать в очередь, но обрабатывать их будет некому.
Внешне это может выглядеть довольно странно: сайт продолжает открываться, пользователи могут создавать записи в базе, но письма не отправляются, отчеты не формируются, а количество ожидающих заданий постепенно растет.
Поэтому production worker должен работать как управляемый фоновый процесс.
Нам нужен механизм, который умеет:
- Запускать worker вместе с системой;
- Перезапускать его после сбоя;
- Хранить статус процесса;
- Собирать логи;
- Корректно останавливать и перезапускать worker при обновлении приложения.
Для этого обычно используют systemd или Supervisor.
Почему для production лучше systemd или Supervisor
Supervisor давно используется для управления Laravel queue workers и позволяет удобно поддерживать сразу несколько процессов.
Systemd решает ту же базовую задачу средствами самой Linux-системы. На современных Ubuntu он уже отвечает за Nginx, PHP-FPM, PostgreSQL и множество других сервисов.
Для нашей схемы отдельный systemd unit проще: не нужно устанавливать еще один менеджер процессов.
Получается единый подход:

Systemd сможет автоматически запустить worker после перезагрузки VPS, восстановить его после аварийного завершения и показать логи через journalctl.
Supervisor при этом остается нормальной альтернативой. Он особенно удобен, когда нужно управлять большим количеством одинаковых workers или проект уже использует Supervisor в существующей инфраструктуре.
В этой статье остановимся на systemd.
Но с долгоживущим worker есть еще один важный нюанс: он не перечитывает весь Laravel-проект перед каждым новым заданием.
Как worker реагирует на обновление приложения
Queue worker — долгоживущий PHP-процесс.
Он загружает Laravel при старте, а затем продолжает получать новые задания из очереди. Поэтому после обновления кода уже работающий worker может какое-то время продолжать использовать старую версию приложения.
Например, deployment меняет класс Job:

Это одна из причин, почему после deployment worker нужно корректно перезапускать.
Laravel позволяет попросить workers завершить текущую работу и выйти после нее. Такой подход лучше жесткого завершения процесса посреди задания.
После этого systemd запускает новый worker уже с обновленным кодом и конфигурацией.
Важно помнить и о config cache. Если вместе с кодом изменился .env или файлы config/, сначала нужно обновить кэш Laravel, а затем перезапустить worker.
В итоге типичная последовательность deployment выглядит так:

Теперь можно оформить worker как постоянный systemd service.
Практика: создаем systemd service для queue worker

Для примера предположим, что очередь уже настроена через QUEUE_CONNECTION в .env, а приложение находится в:
/var/www/laravel-appСоздадим отдельный unit:
sudo nano /etc/systemd/system/laravel-queue.serviceДобавим конфигурацию:
[Unit]
Description=Laravel Queue Worker
After=network.target postgresql.service
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/laravel-app
ExecStart=/usr/bin/php artisan queue:work \
--sleep=3 \
--tries=3 \
--timeout=90
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetUser и Group определяют, от какого Linux-пользователя будет работать worker. В нашей схеме используется www-data, как и для PHP-FPM.
WorkingDirectory указывает каталог Laravel, чтобы команда artisan запускалась именно внутри нужного проекта.
Основная строка:
ExecStart=/usr/bin/php artisan queue:workзапускает постоянный Laravel worker.
Параметр --sleep=3 задает паузу между проверками очереди, если новых заданий нет.
--tries=3 ограничивает число повторных попыток выполнения задания.
--timeout=90 задает максимальное время выполнения одного Job. Если задача может законно работать дольше, это значение нужно увеличить под конкретное приложение.
Restart=always просит systemd запускать worker снова после его завершения — как после сбоя, так и после штатного выхода, например при php artisan queue:restart. Параметр RestartSec=5 добавляет небольшую паузу перед повторным запуском.
После сохранения перечитаем unit-файлы:
sudo systemctl daemon-reloadВключим автоматический запуск и сразу запустим worker:
sudo systemctl enable --now laravel-queueПроверим состояние:
sudo systemctl status laravel-queueДля краткой проверки:
systemctl is-active laravel-queueЛоги worker можно посмотреть через journal:
sudo journalctl -u laravel-queueИли следить за ними в реальном времени:
sudo journalctl -u laravel-queue -fПосле обновления приложения Laravel worker можно корректно перезапустить командой:
php artisan queue:restartОна не обрывает выполняемый Job посередине. Worker завершает текущую задачу, после чего останавливается.
Именно поэтому в нашем systemd unit лучше использовать:
Restart=always
RestartSec=5Systemd заметит завершение процесса и автоматически запустит новый worker уже с обновленным кодом и конфигурацией.
Получается такая цепочка:

Важно также, чтобы у Laravel был рабочий cache driver: сигнал queue:restart хранится через cache приложения.
Основные команды queue worker
| Задача | Зачем | Команда |
| Создать systemd unit | Описать queue worker как постоянный системный сервис | sudo nano /etc/systemd/system/laravel-queue.service |
| Перечитать unit-файлы | Применить новую или измененную конфигурацию systemd | sudo systemctl daemon-reload |
| Запустить worker и включить автозапуск | Запустить обработку очереди и автоматически поднимать worker после reboot | sudo systemctl enable --now laravel-queue |
| Проверить состояние | Убедиться, что worker запущен и не завершается с ошибкой | sudo systemctl status laravel-queue |
| Быстро проверить сервис | Получить только текущее состояние процесса | systemctl is-active laravel-queue |
| Посмотреть журнал | Найти ошибки запуска или выполнения Job | sudo journalctl -u laravel-queue |
| Смотреть журнал в реальном времени | Наблюдать за worker во время обработки заданий | sudo journalctl -u laravel-queue -f |
| Мягко перезапустить Laravel workers | Завершить текущие Job и загрузить новый код после автоматического запуска процессов systemd | php artisan queue:restart |
| Принудительно перезапустить сервис | Сразу остановить текущий worker и запустить новый процесс | sudo systemctl restart laravel-queue |
Теперь задания в очереди не зависят от открытого SSH-сеанса и могут обрабатываться постоянно, в том числе после перезагрузки VPS.
Настраиваем Laravel scheduler
Queue worker уже отвечает за фоновые задания, которые попали в очередь. Но у приложения есть и другой тип задач — те, которые должны запускаться не после какого-то события, а по расписанию.
Например, Laravel может каждую ночь очищать временные данные, раз в час отправлять отчет или каждую минуту проверять внешнюю систему. Для таких сценариев используется scheduler.
Зачем нужен scheduler
Laravel scheduler позволяет описывать расписание внутри самого приложения, а не разносить десятки отдельных cron-заданий по серверу.
Например, приложение может содержать несколько задач:
- Каждые 5 минут → проверить новые данные
- Каждый час → сформировать статистику
- Каждую ночь → удалить старые временные файлы
Все эти правила хранятся в Laravel, рядом с кодом проекта.
Это удобно по нескольким причинам:
- Расписание версионируется вместе с приложением;
- Его проще переносить между серверами;
- Разработчику не нужно вручную поддерживать десятки записей в crontab;
- Вся логика периодических задач остается внутри Laravel.
То есть scheduler можно представить как внутренний диспетчер расписания приложения.
Но чтобы он вообще начал работать, внешний механизм все равно должен регулярно запускать Laravel и спрашивать: есть ли сейчас задача, которую пора выполнить?
Здесь и появляется cron.
Чем Laravel scheduler отличается от cron
Cron — это системный планировщик Linux.
Он ничего не знает о Laravel, Job, Artisan или структуре приложения. Он просто запускает указанную команду в заданное время.
Laravel scheduler работает уровнем выше.
Cron запускает одну команду Laravel:

То есть вместо десятка отдельных cron-записей можно оставить одну:
каждую минуту → спросить Laravel, что нужно выполнить
А уже Laravel сам решает, какие задачи действительно должны запускаться именно в эту минуту.
Например:

Так cron отвечает только за регулярный вызов Laravel, а вся логика расписания остается внутри приложения.
Отсюда вытекает важный принцип: для одного приложения обычно не нужно создавать отдельную cron-запись под каждую задачу.
Почему нужен только один запуск scheduler
Laravel scheduler специально устроен так, чтобы сервер регулярно запускал одну и ту же команду, а приложение уже самостоятельно решало, что делать дальше.
Если начать вручную дублировать отдельные задачи в cron, теряется главное преимущество scheduler.
Кроме того, несколько одинаковых запусков scheduler могут привести к тому, что одна и та же задача стартует одновременно несколько раз.
Например:

Для некоторых операций это безобидно, а для других — нет.
Двойная отправка писем, повторный импорт данных или одновременная очистка одной таблицы уже могут создать проблемы.
Поэтому для одного экземпляра приложения обычно достаточно одного системного триггера scheduler.
Если приложение развернуто уже на нескольких серверах, ситуация становится сложнее: тогда нужно отдельно продумывать защиту от параллельного выполнения. Laravel для этого предоставляет механизмы вроде запуска задачи только на одном сервере.
В нашей схеме приложение работает на одном VPS, поэтому используем один cron entry.
Практика: cron или systemd timer
Для Laravel самый простой и привычный вариант — cron.
Откроем crontab пользователя, от которого должны выполняться команды приложения. В нашей схеме Laravel уже запускается от www-data, поэтому можно редактировать его crontab:
sudo crontab -u www-data -eДобавим одну строку:
* * * * * cd /var/www/laravel-app && /usr/bin/php artisan schedule:run >> /dev/null 2>&1Эта запись означает: каждую минуту переходить в каталог Laravel и запускать scheduler.
Сам Laravel при этом не выполняет все задачи подряд. Он проверяет их расписание и запускает только те, для которых текущее время подходит.
Перед тем как полагаться на cron, полезно проверить scheduler вручную:
cd /var/www/laravel-app
php artisan schedule:listКоманда покажет зарегистрированные задачи и их расписание.
Затем можно вручную выполнить проверку:
php artisan schedule:runLaravel выполнит только те задачи, которые должны запускаться в текущий момент.
Проверить, что cron действительно сохранен, можно так:
sudo crontab -u www-data -lЕсли нужно проверить сам системный cron-сервис:
systemctl is-active cronИли подробнее:
sudo systemctl status cronЕсли проекту удобнее systemd timer, его тоже можно использовать вместо cron. Но для одного Laravel-приложения отдельный timer обычно дает мало преимуществ, поэтому здесь оставим стандартный вариант с cron.
Основные команды scheduler
| Задача | Зачем | Команда |
| Открыть crontab Laravel-пользователя | Добавить системный запуск scheduler | sudo crontab -u www-data -e |
| Запускать scheduler каждую минуту | Дать Laravel регулярно проверять внутреннее расписание | * * * * * cd /var/www/laravel-app && /usr/bin/php artisan schedule:run >> /dev/null 2>&1 |
| Посмотреть задачи Laravel | Проверить, какие расписания зарегистрированы | php artisan schedule:list |
| Запустить scheduler вручную | Проверить выполнение задач без ожидания cron | php artisan schedule:run |
| Посмотреть crontab | Убедиться, что запись сохранена | sudo crontab -u www-data -l |
| Проверить cron | Убедиться, что системный планировщик работает | systemctl is-active cron |
| Посмотреть статус cron | Найти ошибки запуска системного scheduler | sudo systemctl status cron |
Теперь Laravel умеет выполнять и два разных типа фоновой работы: queue worker обрабатывает задания из очереди, а scheduler запускает задачи по расписанию.
Подключаем HTTPS
Приложение уже доступно через Nginx по HTTP, поэтому теперь можно добавить TLS и перевести внешний трафик на HTTPS.
Логика здесь простая: сначала убеждаемся, что домен действительно ведет на VPS и Nginx корректно обслуживает сайт по HTTP, а уже после этого выпускаем сертификат. Так гораздо легче отделить проблемы DNS и веб-сервера от ошибок самого TLS.
В качестве центра сертификации будем использовать популярнейший сервис Let's Encrypt, который предоставляет бесплатные короткоживущие сертификаты с автоматизацией выпуска и продления.
Почему сначала нужен рабочий HTTP
До выпуска сертификата нужно убедиться, что сайт открывается по обычному HTTP.
Это важно сразу по нескольким причинам.
Во-первых, домен должен правильно разрешаться в IP-адрес нашего VPS. Если DNS указывает не туда, центр сертификации не сможет подтвердить, что сервер действительно контролирует этот домен.
Во-вторых, Nginx уже должен принимать запросы на нужный server_name. Иначе Certbot либо не сможет автоматически изменить конфигурацию, либо проверка домена завершится ошибкой.
В-третьих, при стандартной HTTP-проверке Let's Encrypt временно обращается к специальному пути внутри .well-known. Именно поэтому ранее в конфигурации Nginx мы не запрещали этот каталог вместе с остальными скрытыми файлами.
Упрощенно проверка выглядит так:

Если этот путь доступен снаружи и DNS уже указывает на нужный сервер, сертификат можно выпускать.
Поэтому перед следующим шагом полезно проверить:
http://app.example.com
Если приложение открывается по HTTP без ошибок, можно переходить к Certbot.
Как Certbot работает с Nginx
Certbot — это клиент, который автоматизирует получение и установку TLS-сертификатов.
При использовании Nginx он может не только запросить сертификат у Let's Encrypt, но и самостоятельно обновить конфигурацию сайта.
Схема примерно такая:

Обычно Certbot добавляет в server block:
- прослушивание порта 443;
- пути к сертификату;
- путь к приватному ключу;
- рекомендуемые TLS-параметры;
- при необходимости перенаправление HTTP → HTTPS.
После этого Nginx начинает принимать зашифрованные соединения.
Сам TLS при этом терминируется на Nginx.
Получается:

Laravel не занимается расшифровкой TLS самостоятельно. Для него запрос уже приходит от Nginx как обычный внутренний запрос.
Это также означает, что SSL-сертификат относится прежде всего к домену и конфигурации Nginx, а не к самому Laravel.
После выпуска сертификата остается еще один вопрос: что произойдет через несколько месяцев, когда срок его действия подойдет к концу?
Что происходит при автоматическом продлении сертификата
Сертификаты Let's Encrypt имеют ограниченный срок действия, поэтому их нужно регулярно обновлять.
Делать это вручную каждый раз неудобно, поэтому Certbot обычно устанавливает механизм автоматического продления.
Система периодически запускает проверку:

При этом Certbot не выпускает новый сертификат при каждом запуске. Он сначала проверяет, действительно ли текущий сертификат уже достаточно близок к окончанию срока действия.
На Ubuntu механизм продления обычно запускается автоматически через systemd timer или соответствующий сервис пакета Certbot.
После успешного обновления конфигурация сайта обычно не требует ручной правки: пути к сертификату остаются теми же, меняются сами файлы сертификата.
Чтобы не ждать реального окончания срока действия, Certbot позволяет заранее проверить процесс продления в тестовом режиме.
Теперь можно перейти к выпуску сертификата.
Практика: выпускаем сертификат

Установим Certbot и модуль для Nginx:
sudo apt install -y certbot python3-certbot-nginxПеред выпуском еще раз проверим конфигурацию Nginx:
sudo nginx -tИ убедимся, что HTTP действительно отвечает:
curl -I http://app.example.comТеперь запросим сертификат:
sudo certbot --nginx -d app.example.comВо время первого запуска Certbot может попросить указать email и принять условия использования.
После успешной проверки домена сертификат будет получен, а конфигурация Nginx обновлена.
Проверим HTTPS:
curl -I https://app.example.comМожно также открыть сайт в браузере и убедиться, что соединение определяется как защищенное.
Посмотреть выпущенные сертификаты:
sudo certbot certificatesТеперь проверим автоматическое продление в тестовом режиме:
sudo certbot renew --dry-runЭта команда не ждет реального истечения сертификата и не выпускает новый, а проверяет на тестовом окружении с высокими лимитами, сможет ли Certbot пройти весь процесс renewal.
Если установка Certbot использует systemd timer, его состояние можно посмотреть так:
systemctl status certbot.timerА расписание:
systemctl list-timers | grep certbotДалее, как обычно, у нас идёт таблица.
Основные команды HTTPS
| Задача | Зачем | Команда |
| Установить Certbot | Добавить клиент Let's Encrypt и интеграцию с Nginx | sudo apt install -y certbot python3-certbot-nginx |
| Проверить Nginx | Убедиться, что конфигурация корректна до изменения | sudo nginx -t |
| Проверить HTTP | Убедиться, что домен уже доступен до выпуска сертификата | curl -I http://app.example.com |
| Выпустить сертификат | Получить TLS-сертификат и подключить его к Nginx | sudo certbot --nginx -d app.example.com |
| Проверить HTTPS | Убедиться, что сайт отвечает по защищенному соединению | curl -I https://app.example.com |
| Посмотреть сертификаты | Проверить домены и сроки действия | sudo certbot certificates |
| Проверить продление | Убедиться, что автоматический renewal пройдет успешно | sudo certbot renew --dry-run |
| Проверить timer | Убедиться, что автоматическая проверка продления включена | systemctl status certbot.timer |
Теперь внешний доступ к приложению защищен TLS: пользователь подключается к Nginx по HTTPS, а дальше запрос проходит по уже настроенной цепочке Nginx → PHP-FPM → Laravel.
Осталось проверить, насколько вся схема переживает обычную перезагрузку VPS. Дальше убедимся, что после reboot автоматически возвращаются Nginx, PHP-FPM, PostgreSQL, queue worker и само приложение.
Проверяем работу после перезагрузки
Что должно подняться автоматически
После перезагрузки сервера должны автоматически запуститься все постоянные компоненты нашей схемы:
- Nginx;
- PHP-FPM;
- PostgreSQL;
- Laravel queue worker;
- cron для scheduler;
- механизм продления сертификатов Certbot.
Сам Laravel как отдельный daemon не запускается. Веб-часть приложения начинает работать тогда, когда Nginx принимает запрос и передает его в PHP-FPM.
Поэтому после reboot цепочка должна восстановиться примерно так:

Если каждый из этих компонентов включен в автозапуск, администратору не нужно вручную поднимать приложение после обслуживания или перезагрузки сервера.
Теперь стоит понять, какие части чаще всего выпадают из этой цепочки.
Что может не пережить reboot
Обычно проблемы возникают не с системными сервисами, а с тем, что было запущено вручную.
Например, не переживут reboot:
- queue worker, запущенный прямо из SSH без systemd;
- ручная команда, оставленная работать в терминале;
- временный development-сервер;
- процесс, который забыли добавить в systemd;
- пользовательский скрипт без автозапуска.
Может возникнуть и другая ситуация: сервис запускается автоматически, но сразу завершается с ошибкой.
Например, queue worker поднимается раньше нужной зависимости, PHP-FPM не может прочитать конфигурацию или PostgreSQL не стартует из-за проблемы с данными.
Поэтому одной проверки enabled недостаточно. После reboot нужно убедиться, что сервисы не только настроены на автозапуск, но и действительно находятся в состоянии active.
Отдельно стоит помнить про cron. Если запись scheduler сохранена в crontab пользователя www-data, она сама по себе переживет reboot, но системный сервис cron тоже должен быть запущен.
После проверки отдельных сервисов можно пройти уже всю цепочку приложения.
Как проверить всю цепочку после restart
Лучше идти снизу вверх — от системных компонентов к внешнему HTTPS.
Сначала проверяем PostgreSQL и PHP-FPM. Если они не работают, Laravel все равно не сможет нормально обслуживать запросы.
Затем Nginx и queue worker.
После этого смотрим scheduler и только потом проверяем приложение снаружи.
Последовательность можно представить так:

Так проще понять, где именно произошел сбой.
Например, если Nginx запущен, а PHP-FPM нет, внешне это может проявиться как 502 Bad Gateway.
Если всё веб-приложение работает, но queue worker не поднялся, сайт откроется нормально, однако фоновые задания начнут накапливаться.
А если cron не работает, периодические задачи просто перестанут запускаться, хотя HTTP и очереди останутся исправными.
Поэтому после reboot важно проверять не одну страницу в браузере, а всю схему.
Команды финальной проверки
Сначала перезагрузим VPS:
sudo rebootПосле повторного SSH-подключения проверим основные сервисы:
systemctl is-active \
nginx \
php8.5-fpm \
postgresql \
laravel-queue \
cronВ нормальной ситуации для каждого сервиса должно вернуться:
activeЕсли какой-то компонент не запустился, сразу посмотрим его подробный статус:
sudo systemctl status <service-name>И журнал:
sudo journalctl -u <service-name> -n 50 --no-pagerТеперь проверим Laravel:
cd /var/www/laravel-app
php artisan aboutУбедимся, что приложение по-прежнему видит PostgreSQL:
php artisan migrate:statusПроверим scheduler:
php artisan schedule:listИ убедимся, что cron-запись сохранилась:
sudo crontab -u www-data -lТеперь проверим внешнюю часть:
curl -I https://app.example.comЕсли HTTPS отвечает корректно, можно отдельно проверить сертификат:
sudo certbot certificatesИ queue worker:
sudo systemctl status laravel-queueДля начала сведем в одну таблицу постоянные системные сервисы, без которых схема после перезагрузки не восстановится полностью.
Проверка системных сервисов
| Компонент | Зачем проверять | Команда |
| Nginx | Принимает HTTP/HTTPS-запросы | systemctl is-active nginx |
| PHP-FPM | Выполняет Laravel-код | systemctl is-active php8.5-fpm |
| PostgreSQL | Хранит данные приложения | systemctl is-active postgresql |
| Queue worker | Обрабатывает фоновые задания | systemctl is-active laravel-queue |
| Cron | Запускает Laravel scheduler | systemctl is-active cron |
Если все эти сервисы находятся в состоянии active, базовый серверный слой работает. Но этого еще недостаточно: нужно отдельно проверить, что сам Laravel запускается, видит базу, scheduler сохранил расписание, а приложение доступно снаружи по HTTPS.
Проверка приложения

| Проверка | Зачем | Команда |
| Laravel запускается | Убедиться, что PHP и конфигурация работают | php artisan about |
| PostgreSQL доступен | Проверить подключение приложения к базе | php artisan migrate:status |
| Scheduler видит задачи | Проверить внутреннее расписание Laravel | php artisan schedule:list |
| Cron-запись сохранена | Убедиться, что scheduler будет запускаться | sudo crontab -u www-data -l |
| HTTPS работает | Проверить внешнюю цепочку целиком | curl -I https://app.example.com |
| Сертификат существует | Проверить состояние TLS | sudo certbot certificates |
Если после перезагрузки все эти проверки проходят, значит базовая схема действительно автономна и не зависит от ручного запуска процессов.
Дальше можно переходить к типовым неисправностям. Сначала разберем 403 Forbidden — ошибку, которая чаще всего связана с неправильным document root или правами доступа к файлам Laravel.
Исправляем ошибку 403 Forbidden
Почему Nginx может вернуть 403
Ошибка 403 Forbidden означает, что запрос дошел до Nginx, но доступ к ресурсу запрещен.
Для Laravel типичные причины такие:
- Root указывает не на
/public, а на корень проекта; - У пользователя
www-dataнет доступа к одному из каталогов по пути; - Каталог существует, но Nginx не может прочитать его содержимое;
- Index.php недоступен из-за прав;
- В конфигурации есть слишком строгое правило deny;
- Вместо файла Nginx попадает в каталог, для которого не настроен index.
Например, если указать:
root /var/www/laravel-app;вместо:
root /var/www/laravel-app/public;Nginx начинает смотреть на внутреннюю структуру проекта как на веб-корень. Это и небезопасно, и может приводить к странному поведению с доступом к каталогам.
Другой частый сценарий — неправильные права на одном из родительских каталогов.
Даже если public/index.php имеет нормальные права, Nginx должен иметь возможность пройти весь путь:

Если на одном из этих каталогов нет права прохода для пользователя или группы, веб-сервер может вернуть 403.
Поэтому при диагностике важно проверять не только сам файл, но и весь путь до него.
Как отличить ошибку Laravel от ошибки Nginx
Это полезно понять сразу, потому что 403 может появиться на разных уровнях.
Если страницу 403 Forbidden формирует сам Nginx, значит запрос, скорее всего, вообще не дошел до Laravel.
В этом случае нужно смотреть:

Если же Laravel уже запустился и сам запретил действие, например через middleware, policy или authorization, ошибка будет обрабатываться на уровне приложения.
Тогда цепочка уже выглядит так:

Один из самых простых способов отличить уровни — посмотреть логи.
Если проблема у Nginx, запись обычно появится в:
/var/log/nginx/laravel-app.error.logНапример, там могут встречаться сообщения вроде permission denied или directory index ... is forbidden.
Если запрос дошел до Laravel, полезно смотреть:
/var/www/laravel-app/storage/logs/Кроме того, внешний вид страницы тоже иногда подсказывает источник ошибки: стандартная короткая страница Nginx и оформленная ошибка приложения обычно выглядят по-разному. Но полагаться только на внешний вид не стоит — логи надежнее.
Когда становится понятно, что причина именно в доступе к файлам, часто возникает соблазн решить все одной командой chmod 777.
Почему chmod 777 — плохое решение
chmod 777 разрешает чтение, запись и выполнение всем пользователям системы.
В частности 777 обозначает права:
rwxrwxrwxчто значит владелец файла, его группа и все остальные пользователи (!) имеют право читать, редактировать, и запускать (!) файлы.
Это почти всегда слишком широкие права для production.
Если выдать их всему проекту Laravel, любой локальный процесс, который получил доступ к этим файлам, сможет менять:
- PHP-код;
.env;- конфигурацию;
- зависимости;
- публичные файлы.
Особенно опасно давать запись в каталоги, где лежит исполняемый PHP-код. В случае уязвимости веб-приложения злоумышленник потенциально может не только записать временный файл в storage, но и изменить код самого приложения, а потом и выполнить его прямо на сервере.
С другой стороны, слишком строгие права тоже ломают работу Laravel.
PHP-FPM должен иметь возможность записывать данные в:
storage/
bootstrap/cache/Поэтому задача не в том, чтобы сделать весь проект writable, а в том, чтобы дать ровно те права, которые нужны каждому компоненту.
Условно:

Именно поэтому при 403 лучше сначала найти реальную причину, а уже потом менять права точечно.
Команды диагностики и исправления 403
Сначала проверим document root в конфигурации Nginx:
sudo nginx -T | grep -A5 "server_name app.example.com"Нас интересует строка:
root /var/www/laravel-app/public;Проверим, существует ли index.php:
ls -l /var/www/laravel-app/public/index.phpТеперь посмотрим права на весь путь:
namei -l /var/www/laravel-app/public/index.phpЭто особенно полезная команда: она показывает владельца и права каждого каталога по пути до файла.
Проверим сам каталог public:
ls -ld /var/www/laravel-app/publicИ корень проекта:
ls -ld /var/www/laravel-appЕсли проблема связана с рабочими каталогами Laravel, проверим:
ls -ld \
/var/www/laravel-app/storage \
/var/www/laravel-app/bootstrap/cacheПри необходимости восстановим группу www-data:
sudo chgrp -R www-data \
/var/www/laravel-app/storage \
/var/www/laravel-app/bootstrap/cacheИ нужные права:
sudo chmod -R ug+rwX \
/var/www/laravel-app/storage \
/var/www/laravel-app/bootstrap/cacheПосле изменений обязательно проверим конфигурацию Nginx:
sudo nginx -tИ перечитаем ее:
sudo systemctl reload nginxЕсли 403 остается, посмотрим последние сообщения error log:
sudo tail -n 50 /var/log/nginx/laravel-app.error.logПри необходимости можно наблюдать за ним во время повторного запроса:
sudo tail -f /var/log/nginx/laravel-app.error.logПосле этого снова проверим сайт:
curl -I https://app.example.comДля удобства основные сценарии можно свести в одну таблицу:
| Симптом | Вероятная причина | Что проверить |
| Nginx сразу возвращает 403 | Неправильный document root | root должен указывать на /var/www/laravel-app/public |
| В логе есть Permission denied | Nginx или PHP-FPM не может пройти по каталогам | namei -l /var/www/laravel-app/public/index.php |
index.php существует, но сайт не открывается | Недостаточные права на public или родительский каталог | ls -ld /var/www/laravel-app /var/www/laravel-app/public |
| Laravel не может писать логи или cache | Нет записи в runtime-каталоги | Права storage и bootstrap/cache |
| В error log есть directory index ... is forbidden | Nginx попал в каталог вместо входного файла | root, index, try_files |
| 403 появляется только на отдельных маршрутах | Запрос уже дошел до Laravel | Middleware, authorization, policy и Laravel logs |
| Ошибка появилась после изменения Nginx | Некорректное правило location или deny | sudo nginx -T и error log |
Проблема исчезает только после chmod 777 | Реальная ошибка прав замаскирована слишком широким доступом | Владельца, группу и права конкретных каталогов |
Если после проверки root, прав на весь путь и error log причина найдена, 403 обычно удается исправить без расширения доступа ко всему проекту.
Исправляем ошибку 502 Bad Gateway
С 403 Forbidden Nginx обычно упирается в путь, права или правила доступа. 502 Bad Gateway означает уже другую проблему: Nginx принял запрос, но не смог нормально получить ответ от PHP-FPM.
То есть запрос дошел до веб-сервера, однако следующая ступень цепочки не сработала.
Что означает 502 в связке Nginx + PHP-FPM
В нашей схеме Nginx не исполняет PHP-код самостоятельно.
Он передает запросы в PHP-FPM через FastCGI:

Если связь между Nginx и PHP-FPM нарушена, Laravel даже не получает запрос.
Например, Nginx настроен на:
/run/php/php8.5-fpm.sockа PHP-FPM на самом деле создал другой socket или вообще не запущен.
Тогда цепочка обрывается примерно здесь:

Именно поэтому при 502 сначала нужно проверять не маршруты Laravel и не PostgreSQL, а связку Nginx → PHP-FPM.
Следующий шаг — понять, что именно обычно ломает эту связь.
Типичные причины
Самая очевидная причина — PHP-FPM остановлен.
В этом случае Nginx пытается передать запрос, но процесса, который должен принять его через FastCGI, просто нет.
Вторая частая причина — неправильный socket.
Например, после обновления PHP сервер уже использует:
/run/php/php8.5-fpm.sockа в конфигурации Nginx осталась старая строка:
/run/php/php8.4-fpm.sockСам Nginx при этом может запускаться совершенно нормально. Ошибка проявится только тогда, когда пользователь откроет PHP-страницу.
Еще один вариант — socket существует, но Nginx не может к нему обратиться из-за прав доступа.
Проблема может быть и не в Unix socket. Если PHP-FPM настроен на TCP, например 127.0.0.1:9000, а Nginx продолжает искать socket-файл, результат будет тем же.
Также 502 может появиться, если PHP-FPM запустился, но сразу падает из-за ошибки конфигурации или нехватки ресурсов.
Поэтому причины удобно разделить на несколько групп:
- PHP-FPM не работает;
- Nginx использует неправильный upstream;
- socket отсутствует;
- socket недоступен по правам;
- PHP-FPM слушает TCP, а Nginx настроен на socket, или наоборот;
- конфигурация PHP-FPM повреждена;
- процесс завершается из-за нехватки памяти или другой системной проблемы.
Теперь разберемся, как быстро отделить эти варианты друг от друга.
Как быстро понять, где проблема
По всем законам жанра лучше идти от простого к сложному.
Сначала проверяем, работает ли PHP-FPM вообще.
Если сервис находится в состоянии inactive или failed, проблему уже нужно искать на стороне PHP-FPM, а не Nginx.
Если PHP-FPM активен, следующий вопрос — где он принимает запросы.
В нашей конфигурации ожидается:
/run/php/php8.5-fpm.sockНужно убедиться, что этот файл действительно существует.
Далее сравниваем его с fastcgi_pass в Nginx.
Получается простая проверка:

После этого смотрим логи.
Nginx при проблеме со socket часто пишет сообщения вроде:
connect() to unix:/run/php/php8.5-fpm.sock failedЕсли PHP-FPM не может стартовать, причина обычно будет видна уже в его journal.
Например:
sudo journalctl -u php8.5-fpmТак можно довольно быстро понять, на каком именно уровне ломается цепочка.
Если же Nginx и PHP-FPM работают, socket совпадает, а 502 остается, уже имеет смысл смотреть нагрузку, PHP-FPM pool и системные журналы.
Команды диагностики и исправления 502
Сначала проверим состояние PHP-FPM:
systemctl is-active php8.5-fpmЕсли сервис не активен, посмотрим подробный статус:
sudo systemctl status php8.5-fpmИ последние сообщения:
sudo journalctl -u php8.5-fpm -n 50 --no-pagerТеперь проверим socket:
ls -l /run/php/php8.5-fpm.sockЕсли файл существует, посмотрим, что PHP-FPM действительно слушает именно его:
grep -R "^listen =" /etc/php/8.5/fpm/pool.d/Затем проверим fastcgi_pass в Nginx:
sudo nginx -T | grep fastcgi_passДля нашей схемы ожидается:
fastcgi_pass unix:/run/php/php8.5-fpm.sock;Если пути различаются, нужно привести Nginx и PHP-FPM к одному значению.
После изменений сначала проверим конфигурацию Nginx:
sudo nginx -tЗатем перезапустим PHP-FPM:
sudo systemctl restart php8.5-fpmИ перечитаем Nginx:
sudo systemctl reload nginxТеперь снова проверим приложение:
curl -I https://app.example.comЕсли ошибка сохраняется, посмотрим error log Nginx:
sudo tail -n 50 /var/log/nginx/laravel-app.error.logПри необходимости — в реальном времени:
sudo tail -f /var/log/nginx/laravel-app.error.logЕсли в журнале PHP-FPM есть ошибки конфигурации, ее можно проверить отдельно:
sudo php-fpm8.5 -tА если есть подозрение, что процесс завершается из-за нехватки памяти, полезно посмотреть системный журнал:
sudo journalctl -k | grep -i -E 'oom|killed process'Далее как обычно у нас идёт таблица:
| Причина | Как проверить | Решение |
| PHP-FPM остановлен | systemctl is-active php8.5-fpm | Запустить или перезапустить php8.5-fpm |
| PHP-FPM не стартует | sudo systemctl status php8.5-fpm и journalctl | Исправить ошибку конфигурации или системную проблему |
| Неправильный socket в Nginx | sudo nginx -T | grep fastcgi_pass | Указать реальный socket PHP-FPM |
| Socket отсутствует | ls -l /run/php/php8.5-fpm.sock | Проверить, запущен ли PHP-FPM и какой listen используется |
| PHP-FPM слушает другой адрес | grep -R "^listen =" /etc/php/8.5/fpm/pool.d/ | Синхронизировать listen и fastcgi_pass |
| Нет доступа к socket | ls -l /run/php/php8.5-fpm.sock и Nginx error log | Исправить владельца, группу или настройки пула PHP-FPM |
| Ошибка конфигурации PHP-FPM | sudo php-fpm8.5 -t | Исправить конфигурационный файл и перезапустить сервис |
| PHP-FPM завершен системой | journalctl -k | Проверить RAM, OOM и нагрузку на VPS |
Если PHP-FPM активен, socket существует, а fastcgi_pass указывает точно на него, большая часть типичных причин 502 Bad Gateway уже исключена.
После 403 и 502 остается еще одна группа проблем, которая может проявляться менее очевидно: Laravel открывается, но не может записать лог, cache, сессию или временный файл. Поэтому дальше отдельно разберем права доступа к storage, bootstrap/cache и самому проекту.
Исправляем проблемы с правами доступа
Какие каталоги Laravel должны быть writable
Большая часть Laravel-проекта во время обычной работы только читается.
Код приложения, vendor, config, routes и другие исходные файлы не должны постоянно изменяться процессом PHP-FPM.
Запись обычно нужна прежде всего в двух местах:
storage/
bootstrap/cache/storage используется для логов, cache, сессий, временных файлов и скомпилированных представлений.
bootstrap/cache хранит оптимизированные файлы конфигурации и другие данные, которые Laravel создает сам.
Модель примерно такая:

Если у PHP-FPM нет права записи в storage, Laravel может не создать laravel.log, не сохранить файловую сессию или получить Permission denied.
Если недоступен bootstrap/cache, проблемы могут появиться при выполнении config:cache, route:cache или других Artisan-команд.
Поэтому writable-права лучше выдавать точечно именно этим каталогам.
Почему права пользователя deploy и www-data могут конфликтовать
На production-сервере код часто обновляет один пользователь, а выполняет приложение другой.
Например:

Пользователь deploy может создавать новые файлы и каталоги во время обновления приложения.
А PHP-FPM работает от www-data и должен потом иметь возможность читать код и записывать runtime-данные.
Если эти пользователи никак не связаны через группу и права, постепенно появляются конфликты.
Например, deploy создает новый каталог внутри storage, который доступен только ему. Сразу после deployment всё выглядит нормально, но первый запрос от PHP-FPM получает Permission denied.
Возможен и обратный сценарий: www-data создает cache-файл, который затем не может изменить пользователь, выполняющий deployment.
То есть проблема часто не в Laravel как таковом, а в том, что два Linux-пользователя по очереди работают с одними и теми же каталогами.
Один из простых способов избежать этого — использовать общую группу, например www-data, и разрешить ей запись только в runtime-каталоги.
Так код остается под контролем deploy-пользователя, а PHP-FPM получает необходимые права там, где они действительно нужны.
Почему владелец и группа важнее chmod
chmod определяет, какие действия разрешены владельцу, группе и остальным пользователям.
Но сам по себе он не отвечает на вопрос, кто именно считается владельцем и кто входит в нужную группу.
Например, права:
drwxrwxr-xозначают:
- Владелец может читать, писать и заходить в каталог;
- Группа тоже может читать, писать и заходить;
- Остальные — только читать и заходить.
Но если группа у каталога не www-data, PHP-FPM от этих rwx для группы ничего не получит.
Поэтому сначала всегда нужно смотреть:

а уже потом менять chmod.
Например, для runtime-каталогов разумная схема может выглядеть так:
owner: deploy
group: www-data
permissions: ug+rwXТогда deploy-пользователь управляет файлами, а PHP-FPM через группу получает нужную запись.
Это намного лучше, чем без разбора выставлять 777.
Если права продолжают сбиваться после каждого deployment, имеет смысл отдельно настроить наследование группы на каталогах. Но для базовой схемы достаточно сначала привести в порядок владельца, группу и права на storage и bootstrap/cache.
Команды проверки и исправления прав
Сначала посмотрим владельца и группу самого проекта:
ls -ld /var/www/laravel-appПроверим runtime-каталоги:
ls -ld \
/var/www/laravel-app/storage \
/var/www/laravel-app/bootstrap/cacheЧтобы увидеть права более подробно по всему пути:
namei -l /var/www/laravel-app/storageТеперь назначим группе www-data каталоги, в которые Laravel должен писать:
sudo chgrp -R www-data \
/var/www/laravel-app/storage \
/var/www/laravel-app/bootstrap/cacheРазрешим владельцу и группе чтение и запись:
sudo chmod -R ug+rwX \
/var/www/laravel-app/storage \
/var/www/laravel-app/bootstrap/cacheЕсли deployment выполняется отдельным пользователем deploy, его можно добавить в группу www-data:
sudo usermod -aG www-data deployПосле этого пользователю нужно заново войти в shell, чтобы новое членство в группе применилось.
Проверить группы:
groups deployДля самого .env оставим более строгие права:
chmod 600 /var/www/laravel-app/.envПроверим их:
ls -l /var/www/laravel-app/.envТеперь можно протестировать запись от имени www-data.
Например:
sudo -u www-data touch /var/www/laravel-app/storage/test-writeЕсли команда проходит успешно, удалим тестовый файл:
sudo rm /var/www/laravel-app/storage/test-writeТакже полезно проверить Laravel:
cd /var/www/laravel-app
php artisan aboutИ посмотреть, может ли приложение нормально работать с логами и cache.
Основные команды проверки прав
| Задача | Зачем | Команда |
| Проверить владельца проекта | Убедиться, кто управляет кодом | ls -ld /var/www/laravel-app |
| Проверить runtime-каталоги | Посмотреть владельца, группу и права | ls -ld storage bootstrap/cache |
| Проверить весь путь | Найти каталог, на котором обрывается доступ | namei -l /var/www/laravel-app/storage |
Назначить группу www-data | Дать PHP-FPM доступ к runtime-каталогам | sudo chgrp -R www-data storage bootstrap/cache |
| Дать запись владельцу и группе | Разрешить Laravel создавать cache, logs и временные файлы | sudo chmod -R ug+rwX storage bootstrap/cache |
| Добавить deploy-пользователя в группу | Избежать конфликтов между deployment и PHP-FPM | sudo usermod -aG www-data deploy |
| Проверить группы пользователя | Убедиться, что новая группа применена | groups deploy |
Ограничить .env | Защитить credentials и конфигурацию | chmod 600 .env |
| Проверить запись от имени PHP-FPM | Убедиться, что www-data действительно может писать | sudo -u www-data touch storage/test-write |
Если www-data может записывать в storage и bootstrap/cache, а остальной проект не открыт на запись без необходимости, права настроены достаточно аккуратно для нашей схемы.
Теперь основные типовые ошибки разобраны. Дальше можно собрать все компоненты в одну финальную проверку и убедиться, что Laravel, PostgreSQL, Nginx, HTTPS, queue worker и scheduler работают как единая система.
Проверяем развертывание целиком
Системные сервисы
| Компонент | Зачем проверять | Команда |
| Nginx | Принимает HTTP/HTTPS-запросы | systemctl is-active nginx |
| PHP-FPM | Выполняет PHP-код Laravel | systemctl is-active php8.5-fpm |
| PostgreSQL | Хранит данные приложения | systemctl is-active postgresql |
| Queue worker | Обрабатывает фоновые Job | systemctl is-active laravel-queue |
| Cron | Запускает Laravel scheduler | systemctl is-active cron |
Если все сервисы находятся в состоянии active, можно переходить к самому Laravel и проверить уже внутреннюю часть приложения.
Laravel и фоновые задачи
| Проверка | Зачем | Команда |
| Laravel запускается | Проверить PHP, .env и базовую конфигурацию | php artisan about |
| PostgreSQL доступен | Убедиться, что приложение подключается к базе | php artisan migrate:status |
| Scheduler видит задачи | Проверить внутреннее расписание | php artisan schedule:list |
| Cron-запись существует | Убедиться, что scheduler реально будет запускаться | sudo crontab -u www-data -l |
| Queue worker работает | Проверить обработку фоновых заданий | sudo systemctl status laravel-queue |
| Runtime-каталоги доступны | Исключить проблемы с записью | ls -ld storage bootstrap/cache |
Если Laravel запускается, видит PostgreSQL и фоновые механизмы активны, остается проверить путь пользователя снаружи — от DNS и HTTPS до ответа приложения.
Внешний доступ
| Проверка | Зачем | Команда |
| HTTP отвечает | Проверить доступность Nginx | curl -I http://app.example.com |
| HTTPS отвечает | Проверить всю внешнюю цепочку | curl -I https://app.example.com |
| Сертификат существует | Проверить TLS и срок действия | sudo certbot certificates |
| Конфигурация Nginx корректна | Исключить ошибку после последних изменений | sudo nginx -t |
| Error log пуст от критических ошибок | Проверить проблемы Nginx и FastCGI | sudo tail -n 50 /var/log/nginx/laravel-app.error.log |
Если все три уровня проходят проверку, итоговая схема выглядит так:

То есть приложение не только открывается в браузере, но и полностью готово к нормальной работе: база доступна, фоновые задания обрабатываются, расписание запускается, HTTPS работает, а основные сервисы автоматически поднимаются после перезагрузки.
Как подготовить Laravel VPS к production
Технически приложение уже развернуто и работает. Но перед production стоит отдельно пройти по настройкам, которые влияют не столько на сам запуск, сколько на безопасность, восстановление после сбоев и дальнейшее сопровождение.
Здесь уже не требуется менять архитектуру. Нужно проверить, что production-среда не хранит лишнюю отладочную информацию, секреты защищены, резервные копии существуют, а основные ошибки можно быстро найти по логам.
Можно считать это дополнительным, не обязательным разделом с которым мы всё же рекомендуем ознакомиться.
Отключить debug
В production-конфигурации должно быть:
APP_ENV=production
APP_DEBUG=falseAPP_DEBUG=true полезен при разработке, но на публичном сервере может раскрывать слишком много внутренней информации: stack trace, пути к файлам, структуру классов, SQL-фрагменты и другие технические детали.
После изменения .env не стоит забывать про config cache. Если он уже был создан, Laravel может продолжать использовать старое значение.
Поэтому после изменения параметров окружения конфигурацию нужно обновить:
php artisan config:clear
php artisan config:cacheПосле этого можно проверить текущее окружение через php artisan about.
С debug разобрались. Следующий очевидный объект защиты — сам .env, потому что именно там лежит большая часть чувствительных параметров приложения.
Ограничить права на .env
Файл .env не должен быть доступен из браузера, публичного Git-репозитория или случайным локальным пользователям сервера.
В нем могут находиться:
- Пароль PostgreSQL;
APP_KEY;- SMTP credentials;
- API-токены;
- Ключи сторонних сервисов;
- Параметры очередей и cache.
Document root у нас уже указывает на public, поэтому Nginx не должен отдавать .env напрямую.
Но этого недостаточно: права на сам файл тоже стоит ограничить.
Например:
chmod 600 /var/www/laravel-app/.envТак читать и изменять файл сможет только его владелец.
При этом важно не забыть, от какого пользователя запускаются deployment-команды и PHP-FPM. Слишком строгие права без учета владельца тоже могут привести к проблемам, поэтому .env должен быть защищен, но доступен тем процессам, которым он реально нужен.
После защиты secrets стоит проверить и то, от кого вообще выполняются административные команды проекта.
Не запускать Composer и Artisan от root без необходимости
Команды Composer и Artisan часто создают новые файлы:
- Зависимости в vendor;
- Cache;
- Скомпилированные views;
- Временные файлы;
- Логи;
- Результаты миграций и служебных операций.
Если постоянно запускать их через sudo, часть файлов может начать принадлежать root.
После этого обычный deploy-пользователь или www-data уже не сможет их изменять.
Типичная цепочка проблемы выглядит так:

Поэтому Composer и Artisan лучше выполнять от обычного пользователя, которому принадлежит код проекта.
sudo стоит использовать там, где действительно нужны системные права: например, для управления systemd, Nginx, пакетами или владельцами файлов.
Так модель прав остается предсказуемой и не требует постоянно исправлять ownership после каждого deployment.
Следующий production-уровень — восстановление данных.
Настроить резервные копии PostgreSQL и .env
Если сервер потеряется, одного Git-репозитория для восстановления приложения недостаточно.
В репозитории обычно есть код, но нет:
- Данных PostgreSQL;
- Production
.env; - Пользовательских загрузок;
- Локальных secrets;
- Некоторых runtime-файлов.
Поэтому как минимум стоит резервировать базу PostgreSQL и production-конфигурацию.
Для PostgreSQL можно использовать регулярные дампы через pg_dump, а затем отправлять их на отдельное хранилище.
Например, схема может выглядеть так:

Хранить единственную резервную копию на том же VPS не особенно полезно: при потере или компрометации сервера исчезнет и приложение, и backup.
.env тоже нужно сохранять отдельно, но безопасно. Не стоит отправлять его в обычный Git только ради удобства восстановления.
Лучше хранить encrypted backup или использовать отдельный secrets manager.
Самое важное — периодически проверять не только наличие backup-файлов, но и возможность реально восстановить из них базу. Нерабочая резервная копия обнаруживается обычно в самый неподходящий момент.
Помимо backup, production требует нормальной наблюдаемости. Для начала достаточно хотя бы понимать, где искать ошибку.
Следить за логами Nginx, PHP-FPM и Laravel
Проблема может возникнуть на разных уровнях, поэтому одного laravel.log недостаточно.
Nginx показывает ошибки внешнего HTTP-слоя:
/var/log/nginx/laravel-app.error.logLaravel хранит прикладные ошибки в:
/var/www/laravel-app/storage/logs/PHP-FPM пишет сообщения через systemd journal и собственные журналы, в зависимости от конфигурации.
Для очередей используется отдельный журнал systemd:
sudo journalctl -u laravel-queueПолезно сразу разделять диагностику по уровню:

На небольшом VPS этого уже достаточно, чтобы не искать любую ошибку сразу во всех каталогах.
Но с очередями есть еще одна особенность: worker может продолжать работать, а отдельные задания — падать.
Контролировать очередь и failed jobs
Состояние systemd-сервиса active означает только то, что процесс worker запущен.
Это не гарантирует, что все Job выполняются успешно.
Например:

Поэтому помимо самого worker нужно следить за неудачными заданиями.
Laravel позволяет посмотреть их через Artisan:
php artisan queue:failedЕсли проблема исправлена, отдельные Job можно повторно отправить на выполнение.
В production также полезно обращать внимание на:
- Количество failed jobs;
- Длительность выполнения Job;
- Повторяющиеся exceptions;
- Рост очереди;
- Слишком частые retries.
Если очередь используется активно, имеет смысл уже отдельно настраивать мониторинг. Но для небольшого приложения хотя бы регулярная проверка failed jobs — хороший базовый минимум.
После этого остается собрать все production-пункты в один чеклист.
Что проверить перед production
Перед публикацией удобнее пройтись по нескольким группам настроек отдельно. Сначала проверим конфигурацию приложения и права доступа.
Конфигурация и доступ
| Проверка | Что должно быть |
| Laravel environment | APP_ENV=production |
| Debug | APP_DEBUG=false |
.env | Не хранится в публичном Git, права ограничены |
| Document root | Указывает на /var/www/laravel-app/public |
| PostgreSQL | Используется отдельная база и отдельный пользователь |
| Порт PostgreSQL | Не открыт наружу без необходимости |
| Runtime-каталоги | storage и bootstrap/cache доступны приложению на запись |
| Остальной код | Не открыт на запись без необходимости |
Если базовая конфигурация и права настроены корректно, дальше стоит проверить постоянные процессы, от которых зависит работа приложения после reboot.
Сервисы, очередь и HTTPS
| Проверка | Что должно быть |
| PHP-FPM | Работает и использует правильный socket |
| Queue worker | Запущен через systemd и включен в автозапуск |
| Scheduler | Cron-запись существует, задачи видны через schedule:list |
| HTTPS | Сертификат действителен |
| Продление TLS | certbot renew --dry-run проходит без ошибок |
| Reboot | После перезагрузки приложение возвращается без ручного запуска |
Если сервисы переживают reboot и внешний доступ работает стабильно, остается проверить то, что понадобится уже при сбое или восстановлении.
Резервные копии и диагностика
| Проверка | Что должно быть |
| Backup PostgreSQL | Настроен и хранится отдельно от VPS |
Backup .env | Хранится безопасно вне сервера |
| Nginx logs | Доступны для диагностики |
| Laravel logs | Записываются без ошибок прав |
| Failed jobs | Проверяются через queue:failed |
| Восстановление | Backup хотя бы периодически проверяется на пригодность |
Если все три группы закрыты, VPS уже подготовлен не только к запуску Laravel, но и к нормальной эксплуатации.
Заключение

Связка Nginx + PHP-FPM + PostgreSQL + queue worker + scheduler хорошо подходит для небольших и средних Laravel-проектов, которым нужен понятный и контролируемый production-стек без лишней инфраструктурной сложности. Это может быть корпоративный кабинет, внутренний сервис, небольшой интернет-магазин, API, SaaS-проект на ранней стадии или частное приложение с умеренной нагрузкой.
Главное преимущество такой схемы — предсказуемость. Все основные компоненты находятся на одном VPS, их легко проверить, перезапустить и диагностировать. При этом архитектура остается достаточно гибкой: по мере роста проекта можно вынести PostgreSQL на отдельный сервер, подключить Redis, увеличить количество queue workers или добавить балансировку без полного пересмотра приложения.
Если проект пока небольшой, я бы не усложнял инфраструктуру заранее. Гораздо полезнее сначала добиться стабильной работы этой базовой схемы, настроить backup, логи, очереди и проверку после reboot, а масштабирование добавлять уже по реальной нагрузке.
Спасибо за внимание!
FAQ
Можно ли развернуть Laravel без Nginx?
Технически Laravel можно запустить встроенным PHP-сервером или через php artisan serve, но это прежде всего инструмент для разработки. Для production обычно используют полноценный веб-сервер и PHP-FPM.
В нашей схеме Nginx принимает внешний трафик, обслуживает статические файлы и передает PHP-запросы через FastCGI в PHP-FPM.
Обязательно ли использовать PostgreSQL?
Нет. Laravel поддерживает несколько СУБД, а конкретный вариант зависит от проекта. В этой инструкции PostgreSQL выбран как основная база, поэтому мы установили pdo_pgsql и создали отдельного пользователя и базу.
Если приложение уже использует MySQL, MariaDB или другую поддерживаемую СУБД, менять ее только ради этой схемы не нужно.
Почему Laravel открывается, но очередь ничего не выполняет?
Сам сайт и queue worker — разные процессы.
HTTP-запрос может успешно пройти через Nginx и PHP-FPM, даже если worker остановлен. Поэтому отдельно проверяйте laravel-queue, журналы systemd и failed jobs.
Laravel также указывает, что queue:work — долгоживущий процесс и для постоянной работы его нужно держать под контролем process manager.
Нужно ли перезапускать queue worker после каждого deployment?
Если код, которым пользуется worker, изменился — да.
queue:work хранит загруженное состояние приложения в памяти и не замечает изменения кода автоматически. Laravel рекомендует перезапускать workers при deployment; queue:restart позволяет сделать это после завершения текущего Job.
Что делать, если APP_DEBUG=false, а подробности ошибки все равно нужны?
Подробную информацию нужно смотреть не в браузере, а в логах приложения и сервисов.
Для Laravel — в storage/logs, для Nginx — в error log, для PHP-FPM и queue worker — через systemd journal.
Production debug лучше не включать только ради диагностики: официальная документация Laravel прямо предупреждает, что APP_DEBUG в production должен быть выключен.
Почему scheduler запускается каждую минуту, если задача нужна только раз в сутки?
Потому что cron не выполняет все Laravel-задачи каждую минуту. Он лишь запускает schedule:run, а уже Laravel проверяет внутреннее расписание и решает, какие задачи действительно пора выполнять.
Официальная документация Laravel предусматривает именно одну cron-запись для scheduler.
Можно ли использовать Supervisor вместо systemd для очередей?
Да.
Laravel в документации часто приводит Supervisor как пример process manager для queue:work. В нашей инструкции выбран systemd, потому что он уже присутствует в Ubuntu и позволяет управлять worker тем же способом, что и другими сервисами.
Смысл в обоих случаях одинаковый: worker должен автоматически запускаться, восстанавливаться и не зависеть от открытой SSH-сессии.
Почему после изменения .env Laravel продолжает использовать старое значение?
Частая причина — config cache.
Если конфигурация была закэширована, Laravel может продолжать работать с уже собранными значениями. После изменения .env стоит обновить конфигурационный кэш через config:clear и config:cache.
Нужно ли открывать PostgreSQL наружу?
Если Laravel и PostgreSQL находятся на одном VPS — обычно нет.
Приложение может подключаться к базе по 127.0.0.1:5432, а PostgreSQL останется недоступен извне. Отдельную роль приложения при этом все равно стоит использовать вместо административной роли postgres. PostgreSQL разграничивает доступ именно через роли и их privileges.
Достаточно ли pg_dump для резервного копирования production?
Для небольшого проекта он может быть удобной частью backup-схемы, но сам PostgreSQL отмечает, что для регулярного резервного копирования production в нетривиальных сценариях стоит учитывать и другие механизмы backup.
pg_dump экспортирует одну базу; глобальные объекты вроде ролей в него не входят — для них используется pg_dumpall.
Что будет, если Certbot не сможет продлить сертификат?
Существующий сертификат не исчезнет мгновенно — он продолжит работать до окончания срока действия. Но если проблема не будет устранена до expiry, HTTPS начнет выдавать ошибку сертификата.
Поэтому полезно заранее проверять renewal через certbot renew --dry-run. Certbot официально поддерживает renew, Nginx plugin и dry-run для тестирования продления. Не лишним будет и настроить мониторинг статуса сертификата и алертинг при сбоях.



