Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Symfony Maintenance Bundle Laravel Package

in-sys/symfony-maintenance-bundle

A lightweight Symfony bundle for enabling maintenance mode. It lets you show a custom maintenance page, control who can access the app during downtime, and toggle maintenance from the console or configuration—ideal for planned upgrades, deployments, and preventing users from seeing raw errors.

View on GitHub
Deep Wiki
Context7

Getting Started

This is the Symfony equivalent of Laravel's php artisan down / php artisan up — two console commands that flip a flag, plus a request listener that short-circuits traffic with a 503. If you've used artisan down you already know the mental model.

v2.0.0 is a platform bump, not a behaviour change. The release notes are explicit: it drops support for older Symfony and PHP versions to establish a new compatibility baseline (Symfony 6.4 / 7.x). There are no new commands, no new options, no config changes called out — just modernization and code quality work. That means the mental model and command surface from 1.x carry over unchanged, but the install gate is now much narrower.

Minimal setup:

composer require in-sys/symfony-maintenance-bundle:^2.0

Before you run that, confirm your platform actually satisfies the new baseline. Check the package's own composer.json for the exact constraints rather than trusting the changelog prose:

composer show in-sys/symfony-maintenance-bundle --all | grep -A5 requires

If you're on Symfony 5.4 or 6.0–6.3, Composer will refuse to install 2.x. You're either staying on ^1.0 (now effectively frozen — see Gotchas) or doing the Symfony upgrade first. Don't try to do both in one PR.

If Flex doesn't auto-register it, add it to config/bundles.php:

return [
    // ...
    InSys\MaintenanceBundle\InSysMaintenanceBundle::class => ['all' => true],
];

Then confirm the actual command names rather than guessing — the README is the source of truth, but bin/console list never lies:

bin/console list maintenance
bin/console list | grep -i maint

Typical usage is a two-step toggle:

bin/console maintenance:enable     # site now returns 503
bin/console maintenance:disable    # back to normal

Where to look first, in order:

  1. The Command/ directory in the vendor package — that tells you the exact argument list and options. v2 is a good moment to re-read this, because a "modernize" commit sometimes renames or re-signatures commands even when the changelog doesn't say so.
  2. The event listener / subscriber that serves the 503 — this is what you actually care about, because it determines what gets blocked (all requests? only text/html? does it skip /_profiler, /_wdt, assets, health endpoints?). Check whether the v2 modernization changed the listener's priority or the event it subscribes to.
  3. Resources/views/ or templates/ — whether there's a default maintenance page you can override, and whether the override path changed with the Symfony 7 template conventions.

First real-world use case: you're about to run a destructive Doctrine migration or a data backfill on a Symfony app. Enable maintenance, run bin/console doctrine:migrations:migrate --no-interaction, disable. That's the 90% scenario.

Implementation Patterns

Deploy-script wrapping. The value of this bundle isn't the toggle, it's putting the toggle inside your deploy so you get an atomic window. With Deployer:

task('maintenance:on', function () {
    run('{{bin/php}} {{release_path}}/bin/console maintenance:enable');
})->select('role=app');

task('maintenance:off', function () {
    run('{{bin/php}} {{release_path}}/bin/console maintenance:disable');
})->select('role=app');

task('deploy:db', function () {
    // migrations here
});

Wire it as deploy:maintenance:on → deploy:db → deploy:maintenance:off, and always wrap the "off" in a finally-style guard so a failed migration doesn't leave the site dark. Same shape for GitHub Actions or Envoyer hooks.

Upgrading 1.x → 2.0. Because the change is compatibility-only, treat it as a dependency bump with a verification pass rather than a migration:

composer require --dev symfony/phpunit-bridge   # if not already present
composer update "symfony/*" in-sys/symfony-maintenance-bundle --with-all-dependencies
bin/console cache:clear
bin/console list maintenance    # confirm command names are unchanged

Then run your testMaintenanceModeReturns503 test (below) on the new major before merging. The one thing that could silently break in a Symfony major bump is listener wiring — a service that was autoconfigured via a services.yaml convention in 5.4 may need an explicit tag under 7.x. Diff the bundle's Resources/config/ against what you remember.

Custom maintenance page instead of a stack trace. Symfony's default 503 view is stark. Override the template from the bundle (copy it into templates/bundles/InSysMaintenanceBundle/) so it matches your app's branding, includes a Retry-After hint, and links to a status page. Because the listener fires before your controllers, this template must be self-contained — no DB-driven nav, no user session lookups. On Symfony 6.4/7 this also means no deprecated Twig constructs and no webpack-encore tags pointing at entries that may not exist while maintenance is on.

Whitelist yourself before you whitelist anyone else. The single biggest operational need is "maintenance is on but I can still see the site." Depending on what the bundle exposes, you'll do one of: an --allow-ip option on the enable command, a maintenance.yaml config with allow_ips / allow_paths, or a small custom listener with a higher priority that checks $request->getClientIp(). Read the listener source before assuming — and re-read it after the 2.0 upgrade if you decorate it.

Health checks and probes. Your load balancer and uptime monitor will start paging you the moment maintenance is enabled, because /health now returns 503. Either have the bundle's listener skip your health path (usually configurable), or accept that 503 is the correct signal and silence alerts with a maintenance-mode tag. Decide deliberately; don't let it page you at 2 a.m. Symfony 7's health_check-style endpoints and blackfire/mercure probes hit the same issue.

Testing it. Functional test that asserts the toggle actually gates traffic:

public function testMaintenanceModeReturns503(): void
{
    // enable however the bundle exposes it (command, service, or flag file)
    $this->client->request('GET', '/');
    self::assertResponseStatusCodeSame(503);
}

Keep this test — it's your only regression net for the one behaviour the bundle promises, and it's the cheapest way to validate the 2.0 upgrade did not break listener registration.

Gotchas and Tips

Audit the code before production. Still one star and zero dependents, now on its third release in the modernized line. The good news is the surface area is unchanged and tiny (two commands, one listener, maybe a config tree) — 15 minutes with vendor/in-sys/symfony-maintenance-bundle/ is enough to read all of it. Do that, and pay particular attention to where the flag is stored and whether v2 moved it.

The 1.x branch is now a dead end. A major that "drops support for older Symfony and PHP" means 1.0.2 gets no further fixes, including security ones. If your app is pinned to Symfony 5.4 LTS, you need to make an explicit decision: stay on unmaintained 1.x knowingly (and note it in your risk register), or plan the Symfony upgrade. Don't let a stale constraint quietly choose for you.

Flag storage is the thing that bites. If state lives in a cache pool rather than a file, then bin/console cache:clear — which your deploy almost certainly runs, and which you'll definitely run as part of the 2.0 upgrade — can silently drop you out of maintenance mode mid-migration, or worse, leave you stuck in it. Confirm the storage mechanism and make sure your deploy ordering respects it. Symfony 6.4+ added more cache pool configurability; check the bundle isn't now defaulting to a pool name you've remapped.

No secret bypass (probably). Laravel's artisan down --secret="..." gives you a cookie-based bypass URL. This bundle, judging by its scope, almost certainly doesn't. If you need "maintenance on for the public, fully usable for logged-in admins," you're writing a custom listener with priority above the bundle's — kernel.request at, say, priority 40, checking the firewall token and returning early. That's ~20 lines; don't fight the package for it. On Symfony 7 the authenticator stack changed, so grab the token from $request->getSession() or the Security service, not the legacy guard API.

HTTP caching and CDNs will defeat you. A reverse proxy or CDN serving cached HTML won't see your 503 until TTLs expire. Purge the cache as part of the enable step, or configure edge rules to bypass cache during maintenance windows. Also confirm Symfony's own HTTP cache isn't serving stale responses.

Workers and cron aren't covered. A kernel.request listener only affects HTTP. Messenger consumers, scheduled commands, and queue workers keep churning through data during your "downtime." If the migration changes a table those workers touch, stop them explicitly with messenger:stop-workers (or supervisor) in the same deploy step.

Webhooks and API clients break. Third-party callbacks (Stripe, payment providers, GitHub) will get 503s and usually retry — often for hours, sometimes not at all. Whitelist known webhook paths, or schedule maintenance windows outside their retry windows.

Don't rely on remembering to turn it off. The failure mode is a site that's been down for six hours because someone's SSH session died mid-migration. Use the deploy-script guard above, plus an alert on "503 rate > 0 for 10 minutes."

Extension points to check: whether the bundle exposes an event (MaintenanceModeEnabledEvent-ish) so you can hook Slack notifications or Sentry tagging, and whether the listener is a tagged service you can decorate rather than replace. If both are missing, wrapping the console commands in your own app:maintenance:on command that calls the bundle's plus your notification is the pragmatic move.

Version pinning. The constraint is now symfony: ^6.4 | ^7.0 with a single-star maintainer. That's a narrower surface than before, which is good, but it also means the "stray composer update" risk shifts onto the major itself. Pin to ^2.0 deliberately and review the diff before any 3.0 appears — you are still effectively the QA team for this dependency.

When to skip it entirely. If all you need is a flag file plus a 503, a 40-line kernel.request listener with maintenance.enabled in .env is arguably less risk than a dependency with no dependents. This bundle earns its place when you want the CLI ergonomics and don't want to write the listener — but evaluate that honestly at install time, not after you've shipped it. The 2.0 release makes that calculation slightly more favourable (a modern codebase, actively touched) but doesn't change the fundamentals.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle