0% прочитано

Laravel Maintenance Mode під час розгортання: контрольований 503 замість випадкових 500

Під час production-розгортання ICanUp я не хочу показувати користувачам напівоновлений Laravel-застосунок і випадкові помилки 500. На критичний період сайт переходить у режим технічних робіт із контрольованою відповіддю 503, попередньо підготовленою сторінкою, Retry-After та чітким правилом: якщо розгортання завершується помилкою, сайт не відкривається автоматично.

16 вересня 2026 р. 10 хв читанняCI/CD

Під час production-розгортання у ICanUp є короткий проміжок, коли код, Composer dependencies, міграції, кеші та довготривалі процеси можуть оновлюватися не одночасно.

Якщо в цей момент пропускати звичайний трафік, користувач може потрапити саме між двома станами застосунку.

У найкращому випадку він просто оновить сторінку. У гіршому - побачить випадковий 500 Internal Server Error.

Тому на критичній частині розгортання я навмисно переводжу сайт у режим технічних робіт (maintenance mode) і повертаю контрольований HTTP 503 Service Unavailable.

503 під час запланованого короткого технічного вікна є правильною відповіддю сервера. Випадковий 500 означає, що користувач уже потрапив у стан застосунку, який не повинен був бути доступним.
Laravel deployment flow where normal traffic switches to a controlled 503 maintenance page while code, dependencies, migrations, caches and long-running processes are updated
Користувачі бачать стабільну 503-сторінку, поки застосунок переходить зі старого стану в новий.

Проблема не в самому deployment, а в проміжному стані

Окрема команда розгортання може бути правильною, але всі команди разом не виконуються атомарно.

Git уже може містити новий код, а vendor ще старий. Міграція вже могла змінити schema, а Laravel cache ще відповідає попередній версії. Новий frontend build може ще не бути встановлений.

Для користувача це виглядає не як "deployment in progress". Для нього це просто зламана сторінка.

Етап

Що може бути тимчасово неузгоджене

Оновлення Git revision

Новий application code зі старими dependencies

Composer install

Код і vendor можуть бути з різних версій

Frontend build

HTML уже новий, а assets ще старі або відсутні

Міграції

Код і database schema можуть короткий час не збігатися

Перебудова кешів

Laravel може використовувати застарілу configuration або routes

Restart processes

Worker, Reverb або SSR можуть ще працювати зі старим кодом

500 і 503 мають різний зміст

500 Internal Server Error означає, що сервер намагався обробити звичайний запит і не зміг.

503 Service Unavailable означає інше: сервіс тимчасово недоступний і сервер повідомляє про це навмисно.

Під час контрольованого розгортання саме другий варіант описує реальний стан системи.

HTTP status

Що він означає в цьому випадку

500

Користувач потрапив у помилковий стан застосунку

503

Застосунок навмисно тимчасово закритий для оновлення

Я вмикаю режим технічних робіт до критичних змін

Режим технічних робіт має бути активований до заміни application code, dependencies та інших критичних частин.

Якщо ввімкнути його вже після composer install або migrations, частина ризикованого вікна все одно залишиться відкритою.

BASH - Увімкнути контрольований режим технічних робіт
php artisan down \  --render=maintenance \  --retry=60 \  --refresh=15

У цьому варіанті для мене важливі три параметри:

  • --render=maintenance - використати попередньо підготовлене представлення maintenance page.
  • --retry=60 - передати клієнту інформацію, через який час варто повторити запит.
  • --refresh=15 - дозволити сторінці періодично оновлюватися під час короткого технічного вікна.

Чому для мене важливий --render

Найгірший варіант maintenance page - сторінка, яка сама залежить від тих частин Laravel, які зараз оновлюються.

Якщо під час composer install або перебудови cache maintenance response потребує нормального завантаження всього застосунку, вона може зламатися разом із ним.

Тому я використовую попередньо підготовлену сторінку через --render.

Maintenance page повинна бути максимально простою. Її задача - стабільно пояснити тимчасову недоступність, а не повторити весь application layout і всі його залежності.

Що бачить користувач

Я не показую stack trace, назву невдалої команди або інші внутрішні деталі.

Користувачеві достатньо зрозуміти, що сайт тимчасово оновлюється і потрібно спробувати ще раз трохи пізніше.

Приклад повідомлення користувачу
Тривають технічні роботи Ми оновлюємо сайт і незабаром повернемося.Будь ласка, спробуйте оновити сторінку через кілька хвилин.

Під час maintenance я не зупиняю Nginx і PHP-FPM

Щоб повернути контрольований HTTP 503, web stack повинен продовжувати приймати запити.

Тому я не використовую зупинку Nginx або PHP-FPM як спосіб "закрити" сайт на час deployment.

У такому випадку користувач отримав би timeout, connection refused або помилку proxy замість зрозумілої maintenance response.

Критична частина розгортання проходить під 503

Критичне вікно розгортання
Enable maintenance mode        |        vUpdate code        |        vInstall Composer dependencies        |        vInstall / activate frontend build        |        vRun migrations        |        vSynchronize application state

Поки ці кроки не завершилися, я не хочу відкривати звичайний public traffic.

Типовий production flow у мене виглядає так

BASH - Скорочений production deployment flow
php artisan down \  --render=maintenance \  --retry=60 \  --refresh=15 git fetch origingit reset --hard origin/main composer install \  --no-dev \  --no-interaction \  --prefer-dist \  --optimize-autoloader php artisan migrate --force 
Це приклад порядку критичних операцій, а не універсальний deployment script. Frontend build, permissions, symlinks та інші кроки залежать від конкретної схеми розгортання.

Найважливіше правило: помилка не повинна автоматично виконати artisan up

Сам факт, що deployment script завершується, ще не означає, що сайт потрібно повернути користувачам.

Якщо критична команда завершилася помилкою, поточний application state може бути неповним.

У такому випадку я залишаю maintenance mode активним до виправлення проблеми або rollback.

Поведінка при помилці
Deployment command failed        |        vDo NOT expose current application state        |        vKeep HTTP 503 active        |        +-- fix problem        |        +-- rollback        |        vverify again

Чому звичайний trap може бути небезпечним

У shell script легко написати cleanup handler, який завжди виконає php artisan up при завершенні.

Для тимчасових файлів це хороший підхід. Для production traffic - не завжди.

Якщо deployment упав після зміни schema або dependencies, автоматичне відкриття сайту в cleanup може показати користувачам саме той пошкоджений стан, від якого maintenance mode мав захищати.

BASH - При помилці не відкривати сайт автоматично
cleanup_on_failure() {    echo "Deployment failed."    echo "Maintenance mode remains active."} trap cleanup_on_failure ERR

Maintenance mode вимикається лише після перевірок

Я не використовую php artisan up одразу після останньої deployment command.

Спочатку перевіряю, чи застосунок узагалі готовий приймати нормальний трафік.

  • Laravel успішно завантажується.
  • Міграції завершилися без помилки.
  • Laravel caches перебудовані.
  • Frontend assets присутні.
  • Queue worker працює.
  • Reverb працює.
  • SSR process працює.
  • Supervisor не показує критичних process failures.
BASH - Перевірити довготривалі процеси
sudo supervisorctl status

Після цього я повертаю звичайний traffic

BASH - Вимкнути режим технічних робіт
php artisan up

Але навіть після artisan up deployment для мене ще не завершений.

Фінальна HTTP-перевірка виконується вже після artisan up

Після повернення звичайного трафіку я виконую зовнішній HTTP check.

Він перевіряє вже не maintenance page, а реальний public application.

BASH - Перевірити public application після artisan up
curl \  --fail \  --silent \  --show-error \  --location \  https://icanup.com.ua/ \  > /dev/null

Опція --fail потрібна, щоб HTTP 500 не виглядав для shell як успішно виконана команда.

Якщо фінальна перевірка не проходить, сайт знову закривається

Є ще один важливий edge case: усі deployment commands пройшли, artisan up виконаний, але зовнішня перевірка показала проблему.

У такому випадку я не залишаю сайт відкритим лише тому, що pipeline уже майже завершився.

BASH - Повернути 503 при невдалій фінальній перевірці
if ! curl \    --fail \    --silent \    --show-error \    --location \    https://icanup.com.ua/ \    > /dev/nullthen    php artisan down \      --render=maintenance \      --retry=60 \      --refresh=15     exit 1fi

Retry-After теж має значення

HTTP 503 може містити Retry-After, щоб клієнт або crawler розумів, що недоступність тимчасова.

Для короткого контрольованого deployment це точніше, ніж поводитися так, ніби сторінка зламалася назавжди.

У Laravel я задаю цей інтервал через --retry.

503 не повинен перетворюватися на довгу аварію

Режим технічних робіт не є виправданням для довгого простою.

Його задача - зробити короткий небезпечний перехід передбачуваним.

Якщо maintenance mode залишається активним надовго, це вже окремий operational incident, який потрібно діагностувати.

SSR, Reverb і worker теж входять у повернення застосунку

У сучасному Laravel-застосунку HTTP 200 не описує весь стан системи.

У ICanUp після deployment потрібно також повернути в правильний стан queue worker, Reverb і Inertia SSR.

Саме тому перезапуск і перевірка Supervisor processes відбуваються ще до завершення maintenance window.

Maintenance page і health check вирішують різні задачі

Механізм

Його задача

Maintenance mode

Не пустити користувача у проміжний або несправний стан

503 page

Коректно пояснити тимчасову недоступність

Process checks

Переконатися, що довготривалі processes запущені

HTTP health check

Перевірити реальний application response після відкриття сайту

Rollback

Повернути відому стабільну версію, якщо нову відновити швидко не вдається

Це частина CI/CD, а не ручна команда на сервері

Для мене maintenance mode має сенс лише тоді, коли він є частиною передбачуваного deployment flow.

Я не хочу щоразу вручну згадувати, коли запускати artisan down, коли artisan up і що перевірити між ними.

Загальну побудову цього процесу я описувала у матеріалі про реальний Laravel CI/CD pipeline.

Що я не роблю

  • Не залишаю звичайний public traffic відкритим під час критичного оновлення application state.
  • Не показую користувачам випадкові 500, якщо недоступність насправді запланована.
  • Не зупиняю Nginx або PHP-FPM лише заради maintenance window.
  • Не будую maintenance page на складній логіці, яка сама може зламатися під час deployment.
  • Не виконую artisan up автоматично після будь-якого failure.
  • Не вважаю завершення останньої deployment command доказом працездатності.
  • Не відкриваю сайт до перевірки Supervisor processes.
  • Не завершую deployment без фінальної HTTP-перевірки.
  • Не залишаю невдалу фінальну перевірку з відкритим public traffic.

Мій короткий checklist

  • Підготувати просту maintenance page.
  • Увімкнути maintenance mode до критичних змін.
  • Повернути HTTP 503, а не 200.
  • Передати Retry-After через --retry.
  • Виконати оновлення code, dependencies, migrations і caches.
  • Перезапустити довготривалі processes.
  • Перевірити Supervisor status.
  • При failure залишити maintenance mode активним.
  • Виконати artisan up лише після внутрішніх перевірок.
  • Після відкриття сайту виконати зовнішній HTTP health check.
  • Якщо він не проходить, знову повернути сайт у maintenance mode.

Під час розгортання краще навмисно показати користувачу короткий 503, ніж випадково відкрити йому напівоновлений застосунок.

Що я залишила собі на майбутнє

  • Розглядати maintenance mode як межу між стабільною старою та стабільною новою версією.
  • Вмикати 503 до початку критичних змін.
  • Використовувати попередньо підготовлену maintenance page.
  • Не покладатися на повне завантаження Laravel для сторінки технічних робіт.
  • Не відкривати сайт автоматично після невдалого deployment.
  • Перевіряти довготривалі processes до повернення public traffic.
  • Після artisan up виконувати окрему зовнішню перевірку.
  • При невдалій фінальній перевірці повертати maintenance mode.
  • Тримати recovery і rollback procedure окремо від звичайного успішного flow.

Висновок

У ICanUp режим технічних робіт став для мене не декоративною сторінкою, а частиною production safety.

Його задача проста: не дозволити користувачу потрапити в проміжний стан між старою та новою версією застосунку.

На критичний період сайт навмисно повертає 503 Service Unavailable. Код, dependencies, migrations, caches, queue worker, Reverb та SSR оновлюються за закритим traffic.

Якщо щось завершується помилкою, maintenance mode залишається активним. Якщо внутрішні перевірки проходять, сайт відкривається, після чого виконується ще одна зовнішня HTTP-перевірка.

Для мене правильне розгортання - це не відсутність кількох секунд 503. Це відсутність випадкового пошкодженого стану, який користувач узагалі може побачити.