maintenance-bundle

Close the site. Not for your team.

A Symfony bundle for maintenance mode with named levels. Visitors get a 503, while the people you choose keep working, by IP, by role or with a signed cookie.

$ composer require mathis-vincent/maintenance-bundle

PHP 8.2+ · Symfony 6.4, 7.x and 8.x · MIT license

$ php bin/console maintenance:activate admin --duration=7200
A visitor503
503

We'll be right back.

Back in 2 hours

Your own Twig template
No cookie, no access
ROLE_ADMIN200
Set-Cookie: maintenance_auto_bypass=…

Who gets through

Each request runs after the security firewall and goes through the active level's rules, in this order.

  1. Route in excluded_routesOpen to everyone, no cookie issued
  2. Valid bypass cookieThrough
  3. IP in allowed_ipThrough, cookie issued
  4. Role in allowed_rolesThrough, cookie issued for the role's lifetime
  5. Anyone elseA 503 with the level's template

What's in the box

No database and no cache to set up: the state is a JSON file in var/, read on each request.

As many levels as you need
Name them deploy, db_migration or partners_only, each with its own access rules and templates.
Signed bypass cookies
HMAC-SHA256 with kernel.secret; secure and httpOnly. A cookie only works for the level it was issued for, so switching level invalidates it.
Reopens by itself
With --duration, the site comes back on time even if you've lost access to the server. No cron or worker involved.
Announce it first
maintenance:announce gives you a date to show visitors. It never starts the maintenance.
A banner for those still inside
maintenance_banner() reminds them that maintenance is on and who can reach the page, in English or French, or in your own words.
A 503 that doesn't break
If your maintenance template fails to render, a plain fallback page still goes out with the right status.

Your look, your words

The bundle imposes no design: the maintenance page and the banner are Twig templates from your project, and each level can have its own.

Visuals

Write the page like any other template, with your own HTML and CSS.

{# templates/maintenance/503.html.twig #}
<link rel="stylesheet" href="/maintenance.css">

<h1>We'll be right back</h1>

{% if maintenance_end() %}
    <p>Back on
        {{ maintenance_end()|date('d/m/Y H:i') }}</p>
{% endif %}

Translations

English and French are included. Change any text or add a language from your project.

# translations/maintenance.fr.yaml
maintenance:
  status:
    page:
      everyone: 'tout le monde'
      nobody: 'personne'
      roles: '%roles%'
      ips: 'les IP autorisées'
      roles_and_ips: '%roles%, les IP autorisées'

Quick start

Four steps, then one command whenever you need it.

  1. Install the bundle.

    $ composer require mathis-vincent/maintenance-bundle
  2. Register it in config/bundles.php.

    MathisVincent\MaintenanceBundle\MaintenanceBundle::class => ['all' => true],
  3. Declare your levels in config/packages/maintenance.yaml.

    maintenance:
      role_lifetimes:
        ROLE_ADMIN: 604800  # bypass cookie lifetime in seconds: 7 days
    
      levels:
        default:            # blocks everyone
          template: 'maintenance/503.html.twig'
    
        admin:
          template: 'maintenance/503.html.twig'
          banner_template: 'maintenance/banner.html.twig'
          allowed_ip: ['%env(ADMIN_IP)%']
          allowed_roles: ['ROLE_ADMIN']
          excluded_routes: ['app_home']

    The roles in role_lifetimes and allowed_roles are your Symfony security roles: the ones your users hold and the ones you define under role_hierarchy in config/packages/security.yaml. The bundle doesn't create any.

    # config/packages/security.yaml
    security:
      role_hierarchy:
        ROLE_ADMIN: ROLE_USER
        ROLE_SUPER_ADMIN: ROLE_ADMIN

    The hierarchy applies: here, a ROLE_SUPER_ADMIN user also gets through allowed_roles: ['ROLE_ADMIN'], with the ROLE_ADMIN lifetime. A role without a lifetime gets 30 days.

  4. Show the banner in templates/base.html.twig.

    <body>
        {{ maintenance_banner() }}
        {% block body %}{% endblock %}
    </body>
  5. Run it.

    Start the generic maintenance (the default level)
    $ php bin/console maintenance:activate
    Or start a specific level
    $ php bin/console maintenance:activate admin
    Or let it reopen by itself after 2 hours
    $ php bin/console maintenance:activate admin --duration=7200
    Announce a date to visitors, without starting anything
    $ php bin/console maintenance:announce "2026-12-01 22:00"
    Check the current state
    $ php bin/console maintenance:status
    Reopen the site
    $ php bin/console maintenance:deactivate