Как развернуть Laravel на VPS с Nginx, PostgreSQL, очередями и HTTPS

Валерий Волков

Время прочтения 54 минуты

И снова добро пожаловать!

Прежде чем переходить к серверу, коротко разберемся, что вообще будем разворачивать.

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_password

DB_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_db

PostgreSQL запросит пароль laravel_user.

Если соединение установлено, можно дополнительно проверить текущую базу и пользователя:

    SELECT current_database(), current_user;

Ожидаемый результат должен соответствовать созданным значениям:

current_databasecurrent_user
laravel_dblaravel_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:

  • .env
  • config/
  • 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_password

Storage используется уже во время работы приложения. 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.php

fastcgi_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;

Она читается примерно так:

  1. Сначала проверить, существует ли файл с таким путем.
  2. Затем проверить, существует ли соответствующий каталог.
  3. Если ничего не найдено — передать запрос в 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.com

APP_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.target

User и 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=5

Systemd заметит завершение процесса и автоматически запустит новый 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:run

Laravel выполнит только те задачи, которые должны запускаться в текущий момент.

Проверить, что 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=false

APP_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.log

Laravel хранит прикладные ошибки в:

    /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 для тестирования продления. Не лишним будет и настроить мониторинг статуса сертификата  и алертинг при сбоях.

Список источников

  1. Laravel Documentation — Deployment
  2. Laravel Documentation — Queues
  3. Laravel Documentation — Task Scheduling
  4. Nginx Documentation — ngx_http_fastcgi_module
  5. Certbot Documentation

Подпишитесь на нашу рассылку и получайте статьи и новости

    Ознакомьтесь с другими нашими материалами