Режим maintenance в Laravel: ошибки, которые кладут сайт
php artisan down выключает сайт для всех — включая деплой-скрипт, healthcheck балансировщика и вас самих. Разбираем, почему это ломается и как настроить обслуживание без белого экрана 503 для всех подряд.

Вы выкатываете релиз, запускаете php artisan down, начинается миграция — и в этот момент CI-раннер, который должен прогнать artisan migrate, получает страницу 503 и падает. Или балансировщик снимает под с ротации, потому что healthcheck теперь отдаёт 503. Или вы сами не можете зайти проверить результат, потому что забыли добавить свой IP в исключения. Режим обслуживания в Laravel выглядит как одна команда, но у неё есть несколько мест, где всё идёт не так. Разберём их на Laravel 10.x и 11.x.
Что вообще делает artisan down
Команда php artisan down кладёт в хранилище файл-флаг. В Laravel 8+ это storage/framework/down с JSON внутри: время, секрет для обхода, код ответа, путь к шаблону. Пока файл существует, HTTP-мидлвар PreventRequestsDuringMaintenance перехватывает каждый входящий запрос и бросает HttpException с кодом 503.
Ключевое: флаг лежит на диске конкретного сервера. Если у вас несколько инстансов приложения за балансировщиком, artisan down на одном из них выключит только этот инстанс. Про это забывают чаще всего.
Режим обслуживания в Laravel — это не глобальный рубильник. Это файл на диске одного процесса. В горизонтально масштабированной инфраструктуре он ведёт себя не так, как вы ожидаете.
Ошибка 1. Деплой-скрипт сам себя блокирует
Классика: скрипт деплоя делает down, потом composer install, migrate, config:cache, потом up. Проблема в том, что часть artisan-команд во время выполнения обращается к приложению по HTTP — либо вы сами дёргаете эндпоинт для прогрева, либо очередь пытается достучаться до API. Такой запрос упирается в 503.
Сами консольные команды artisan режим обслуживания не блокирует — мидлвар работает только на HTTP-слое. Но всё, что ходит через HTTP во время обслуживания, отвалится. Решение — секрет обхода:
# Laravel 10.x и 11.x
php artisan down --secret="deploy-bypass-token"
После этого запрос на https://example.com/deploy-bypass-token ставит cookie, и клиент с этой cookie проходит сквозь режим обслуживания. Но для сервер-сайд запросов из скрипта cookie неудобна. Часто проще не делать down вовсе, а использовать деплой без даунтайма (об этом ниже).
Ошибка 2. Балансировщик снимает под с ротации
Healthcheck от Nginx upstream, ALB или Kubernetes обычно дёргает какой-нибудь /health или /. При включённом обслуживании этот путь тоже отдаёт 503, балансировщик считает инстанс мёртвым и выводит его из ротации. Если вы включаете down поочерёдно на всех подах — трафику некуда идти.
Начиная с Laravel 8 в down можно передавать код ответа и параметры повтора. Отдавайте не 503, а что-то, что healthcheck считает живым, либо выносите healthcheck-эндпоинт из-под мидлвара. Второе надёжнее.
Как исключить пути из режима обслуживания
В Laravel 10.x свойство задаётся в app/Http/Middleware/PreventRequestsDuringMaintenance.php:
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
class PreventRequestsDuringMaintenance extends Middleware
{
// Эти пути отвечают даже во время обслуживания
protected $except = [
'health',
'webhooks/*',
];
}
В Laravel 11.x отдельного файла мидлвара по умолчанию нет — конфигурация мидлваров переехала в bootstrap/app.php. Исключения задаются там:
<?php
// bootstrap/app.php, Laravel 11.x
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->preventRequestsDuringMaintenance(except: [
'health',
'webhooks/*',
]);
})
->create();
Точный набор методов конфигуратора мидлваров сверяйте с документацией вашей минорной версии — API bootstrap/app.php в 11.x дорабатывался между релизами.
Ошибка 3. Кеш конфигурации оставил сайт выключенным
Если во время деплоя вы вызываете config:cache при уже включённом обслуживании, а up отрабатывает не полностью (скрипт упал на миграции), сайт остаётся лежать. Хуже, если оркестратор перезапустил контейнер: storage/framework/down в новом контейнере отсутствует, приложение поднялось, но старый под ещё отдаёт 503. Пользователи ловят то работающий, то нерабочий ответ.
Проверять состояние стоит явно:
# Laravel 10.x+ — покажет, включён ли режим
php artisan about | grep -i maintenance
# либо проверить наличие флага напрямую
ls -la storage/framework/down
Ошибка 4. down не переживает shared-хостинг и права
Файл флага пишется в storage/framework. Если веб-сервер работает от одного пользователя, а деплой-скрипт — от другого, вы можете создать флаг, который веб-процесс не сможет удалить командой up. Симптом: up отработал без ошибки, а сайт лежит. Проверяйте, что права на storage/framework позволяют писать и HTTP-пользователю, и деплой-пользователю.
Zero-downtime вместо down: чем платить
Для большинства релизов режим обслуживания не нужен вообще. Если деплой атомарный (симлинк на новый релиз, как в Deployer или Envoyer), пользователь не видит промежуточного состояния. down оправдан только когда миграция несовместима со старым кодом — например, переименование колонки, которое сломает работающие поды.
| Подход | Что выигрываете | Чем платите |
|---|---|---|
artisan down на весь релиз | Простота, гарантия, что никто не работает во время миграции | Даунтайм для всех, риск оставить сайт лежать, проблемы с балансировщиком |
| Atomic deploy (симлинк), без down | Ноль даунтайма | Нужна инфраструктура; несовместимые миграции ломают старые поды |
| Expand/contract миграции + без down | Ноль даунтайма даже при смене схемы | Миграция в два-три релиза, больше дисциплины |
Когда что выбирать
- Один сервер, редкие релизы, некритичный сайт —
artisan down --secretдостаточно. - Несколько инстансов за балансировщиком — atomic deploy,
downлибо не трогайте, либо исключайте healthcheck. - Меняете схему БД так, что старый код упадёт — expand/contract: сначала добавили новое, задеплоили код, потом убрали старое. Отдельная большая тема, здесь не разворачиваем.
Что проверить перед тем, как жать down в проде
- Секрет обхода задан:
artisan down --secret="...", и вы знаете URL для входа. - Healthcheck-путь исключён из мидлвара (
$exceptв 10.x илиpreventRequestsDuringMaintenanceв 11.x), иначе балансировщик выкинет под. - Права на
storage/frameworkпозволяют и HTTP-, и deploy-пользователю писать и удалять флаг. - В деплой-скрипте
upвызывается в блоке гарантированного выполнения (trap/finally), чтобы упавшая миграция не оставила сайт выключенным. - Кастомный шаблон 503 существует и рендерится без обращения к БД — иначе получите ошибку внутри страницы обслуживания.
Что делать дальше
Если релизы у вас частые, следующий шаг — уйти от down к атомарному деплою: посмотрите Laravel Envoyer или откройте бесплатный Deployer с рецептом для Laravel. Настройте отдельный лёгкий healthcheck-роут вне мидлвара обслуживания и заведите его в балансировщик. И добавьте в мониторинг алерт на то, что сайт вернул 503 дольше N секунд — это ловит именно случай «забыли сделать up».
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: Laravel Documentation — Configuration: Maintenance Mode, Laravel Documentation — Deployment
Частые вопросы
А если у меня Laravel 11, а в статье про 10 — исключения задаются по-другому?
Блокирует ли режим обслуживания консольные artisan-команды?
Я сделал up, а сайт всё ещё отдаёт 503. Что не так?
Как самому зайти на сайт во время обслуживания?
Грозит ли down даунтаймом для всех, если у меня балансировщик?
Можно ли отдавать не 503, а другой код во время обслуживания?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.