mathis-vincent / maintenance-bundle
A highly flexible and fully configurable Symfony bundle to handle multiple types of maintenance modes seamlessly.
Package info
github.com/Mathis-Vincent/maintenance-bundle
Type:symfony-bundle
pkg:composer/mathis-vincent/maintenance-bundle
Requires
- php: >=8.2
- symfony/config: ^6.4 || ^7.0 || ^8.0
- symfony/console: ^6.4 || ^7.0 || ^8.0
- symfony/dependency-injection: ^6.4 || ^7.0 || ^8.0
- symfony/event-dispatcher: ^6.4 || ^7.0 || ^8.0
- symfony/http-kernel: ^6.4 || ^7.0 || ^8.0
- symfony/security-bundle: ^6.4 || ^7.0 || ^8.0
- symfony/translation: ^6.4 || ^7.0 || ^8.0
- symfony/yaml: ^6.4 || ^7.0 || ^8.0
- twig/twig: ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- phpunit/phpunit: ^11.5
- symfony/browser-kit: ^6.4 || ^7.0 || ^8.0
- symfony/framework-bundle: ^6.4 || ^7.0 || ^8.0
- symfony/twig-bundle: ^6.4 || ^7.0 || ^8.0
- vimeo/psalm: ^6.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-develop
- 1.2.0
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
- dev-feature/26-role-provider-followup
- dev-feature/26-optional-security-bundle
- dev-feature/27-test-lowest-dependencies
- dev-feature/28-psalm-level-one
- dev-feature/31-add-changelog
- dev-feature/30-add-dependabot
- dev-feature/17-json-503-response
- dev-feature/32-security-and-templates
- dev-feature/34-dispatch-maintenance-events
- dev-feature/19-document-file-storage
- dev-feature/25-retry-after-header
- dev-feature/16-invalidate-bypass-cookies
- dev-main
- dev-feature/readme-website-link
- dev-feature/release-guard
- dev-feature/29-protect-branches
- dev-docs/readme-logo-homepage
- dev-feat/announce-duration
- dev-feat/status-command
- dev-feat/excluded-routes
- dev-feat/Manage-Symfony-6-to-8
This package is auto-updated.
Last update: 2026-11-09 12:57:42 UTC
README
Symfony Maintenance Bundle
A robust, modern, and fully configurable Symfony bundle to manage application maintenance mode.
It supports multi-level configuration, cryptographic protection via HMAC signing (using kernel.secret), IP address and user role restrictions, as well as automatic secure cookie bypass generation mapped directly to user roles.
Requirements: PHP 8.2+ and Symfony 6.4, 7.x or 8.x.
📋 Table of Contents
- Global Concept
- Installation & Integration
- YAML Configuration
- Console Commands
- Displaying the Maintenance Banner
- Reacting to Maintenance Changes
- Contributing
- License
1. Global Concept
This bundle allows you to put your application in maintenance mode without interrupting the work of your administrators or support teams.
Key Features:
- Modular Maintenance Levels: Configure multiple levels (e.g.,
default,admin,help_desk) with custom permissions and templates. - Automatic Role-Based Cookie Bypass: Automatically generates a cryptographically signed secure cookie when an authorized user accesses the application.
- Cookies Reset Per Maintenance: Each activation gets a unique ID signed into the cookie, so cookies from a previous maintenance stop working.
- Role-Based Lifetimes: Configure cookie duration globally per user role (e.g., 7 days for
ROLE_ADMIN, 30 days forROLE_HELP_DESK). - Hybrid Restrictions: Flexible combination of rules by IP address, user roles (
ROLE_ADMIN,ROLE_SUPPORT), and/or cookies. - Persistent Warning Banner (UX): Displays a customizable banner for authorized users navigating during maintenance.
Why the state is a file, not a database
The maintenance state lives in var/maintenance.json (and var/maintenance_announcement.json), never in the database. Maintenance must stay available when the database is down: a database-backed state would make activation fail and every state check depend on it. The bundle therefore has no Doctrine dependency.
2. Installation & Integration
A. Download the Bundle
Just by running this command in your terminal :
composer require mathis-vincent/maintenance-bundle
B. Register the Bundle (config/bundles.php)
Ensure the bundle is registered in your config/bundles.php file:
return [ // ... MathisVincent\MaintenanceBundle\MaintenanceBundle::class => ['all' => true], ];
C. Base Template Integration (templates/base.html.twig)
To display the maintenance warning banner to authorized users while maintenance is active, insert the Twig function at the top of your HTML document body:
<!DOCTYPE html> <html> <head> <title>{% block title %}My Application{% endblock %}</title> </head> <body> {# Automatic display of the maintenance banner #} {{ maintenance_banner() }} {% block body %}{% endblock %} </body> </html>
3. YAML Configuration
Create or edit the file config/packages/maintenance.yaml to define role lifetimes and access rules:
Level names are up to you.
default,adminandhelp_deskbelow are only examples: declare as many levels as you need underlevels, with any name (e.g.deploy,db_migration,partners_only). The only special name isdefault, whichmaintenance:activateuses when no level is given.
maintenance: # Global cookie lifetimes per role (in seconds) role_lifetimes: ROLE_ADMIN: 604800 # 7 days ROLE_HELP_DESK: 2592000 # 30 days levels: # Example levels — rename, remove or add your own. # "default": blocks all visitors without exception default: template: "maintenance/503.html.twig" # "admin" (example): allows specified IPs and ROLE_ADMIN users admin: allowed_ip: - '%env(ADMIN_IP)%' allowed_roles: - 'ROLE_ADMIN' excluded_routes: - 'app_home' template: "maintenance/503.html.twig" banner_template: "maintenance/banner.html.twig" # "help_desk" (example): extends access to support teams help_desk: allowed_ip: - '%env(ADMIN_IP)%' - '%env(HELP_DESK_IP)%' allowed_roles: - 'ROLE_ADMIN' - 'ROLE_HELP_DESK' template: "maintenance/503.html.twig" banner_template: "maintenance/banner.html.twig"
Configuration Options:
| Option | Type | Description |
|---|---|---|
role_lifetimes |
array |
Key-value mapping of Symfony user roles to bypass cookie lifetimes in seconds. |
allowed_ip |
array |
List of IP addresses allowed to bypass maintenance mode. |
allowed_roles |
array |
List of Symfony roles allowed to bypass maintenance mode. |
excluded_routes |
array |
Route names (exact match) that stay available to everyone at this level. |
template |
string |
Twig template path for the 503 HTTP response displayed to blocked users. |
banner_template |
string |
Twig template path for the warning banner displayed to authorized users. |
Which roles to use:
The roles in role_lifetimes and allowed_roles are your Symfony security roles: the ones your users hold (getRoles()) and the ones you define under role_hierarchy in config/packages/security.yaml. The bundle does not create any role, and a role your users hold directly does not need to be listed in role_hierarchy.
symfony/security-bundle is optional. When it is installed and enabled, the bundle reads the roles of the logged-in user from it. Without it, set role_provider to a service implementing MathisVincent\MaintenanceBundle\Service\MaintenanceRoleProviderInterface (getRoles(Request $request): array returns the role strings of the requester); you can also use it to override the default. Set it at the top level of the maintenance config, not per level. Using allowed_roles with neither available fails when the container is compiled. IP and cookie bypass need none of this.
# config/packages/security.yaml security: role_hierarchy: ROLE_HELP_DESK: ROLE_USER ROLE_ADMIN: ROLE_HELP_DESK
With the default provider, allowed_roles are matched against the roles of the security token expanded by the hierarchy; custom voters are not consulted. With the hierarchy above, allowed_roles: ['ROLE_HELP_DESK'] also lets ROLE_ADMIN users through.
The cookie lifetime comes from the first role of allowed_roles that the user is granted, not from the role they actually hold. In that same example, a ROLE_ADMIN user gets the ROLE_HELP_DESK lifetime. To give them their own lifetime, list ROLE_ADMIN first in allowed_roles. A role without an entry in role_lifetimes gets 30 days.
Example of a custom provider:
maintenance: role_provider: App\Maintenance\MyRoleProvider
4. Console Commands
The bundle provides four console commands under the maintenance:* namespace.
A. Activate Maintenance Mode
# Activate maintenance mode with default level ("default") php bin/console maintenance:activate # Activate maintenance mode for any level declared in your config (e.g., "admin") php bin/console maintenance:activate <level> # End the maintenance by itself after a duration in seconds (here, 2 hours) php bin/console maintenance:activate <level> --duration=7200
Validation: Activating an undeclared level (e.g.,
php bin/console maintenance:activate non_existent) will raise an error and fail gracefully.
With --duration, the site reopens at the end date even if you can no longer reach the server: no cron or worker is involved, the date is checked on each request. The 503 response then carries a Retry-After header (seconds until the end date); without --duration it is omitted. The end date can be shown to visitors, see Showing the end date.
B. Deactivate Maintenance Mode
php bin/console maintenance:deactivate
C. Check Maintenance Status
php bin/console maintenance:status
Displays whether maintenance mode is active, with which level and until when, and any announced maintenance. It also warns when the active level is not declared in your config, since requests are not blocked in that case. The command always exits with 0.
D. Announce a Maintenance
# Announce a date (anything PHP's strtotime() understands, in the server's timezone) php bin/console maintenance:announce "2026-12-01 22:00" # Remove the announcement php bin/console maintenance:announce --cancel
An announcement only informs visitors, see Showing an announcement: it does not start the maintenance, you still run maintenance:activate when the time comes. It disappears by itself once the date has passed, or when a maintenance is activated.
5. Displaying the Maintenance Banner
The Twig extension (MaintenanceTwigExtension) provides five global functions:
{{ maintenance_banner() }}: Renders the configured banner template (banner_template) if maintenance mode is active. On a route listed inexcluded_routes, it only renders for people who can bypass the level (IP, role or HMAC cookie).{{ maintenance_is_active() }}: Returnstrueif maintenance mode is currently active.{{ maintenance_level() }}: Returns the name of the active maintenance level (e.g.,admin).{{ maintenance_end() }}: Returns the end date of the active maintenance as aDateTimeImmutable,nullwithout--duration.{{ maintenance_announcement() }}: Returns the announced date as aDateTimeImmutable,nullwhen nothing is announced.
Behavior Overview:
- Normal Mode:
{{ maintenance_banner() }}returns an empty string. - Maintenance Active (
maintenance:activate admin):- Unauthenticated or unauthorized visitors receive the 503 HTTP status page, or a
{"error":"maintenance"}JSON 503 when theAcceptheader prefersapplication/json. - Authorized users (via IP, role, or HMAC cookie) navigate seamlessly and automatically receive a signed cookie mapped to their role duration.
- Unauthenticated or unauthorized visitors receive the 503 HTTP status page, or a
Showing who can reach the page:
The banner template receives status.page, a translatable message saying who can reach the current route. Print it with the trans filter:
<div class="maintenance-banner">Maintenance in progress. This page is accessible by: {{ status.page|trans }}</div>
The bundle ships English and French translations in the maintenance domain. To change a text or add a language, declare the keys in your project, for example in translations/maintenance.fr.yaml:
maintenance: status: page: everyone: 'tout le monde' # excluded route nobody: 'personne' # no allowed role or IP roles: '%roles%' # allowed roles only ips: 'les IP autorisées' # allowed IPs only roles_and_ips: '%roles%, les IP autorisées'
%roles% is the comma-separated list of the level's allowed_roles. IP values are never displayed.
Showing the end date:
In your maintenance page (template) or banner:
{% if maintenance_end() %}
<p>Back on {{ maintenance_end()|date('d/m/Y H:i') }}</p>
{% endif %}
Showing an announcement:
In your base template, so that every visitor sees it:
{% if maintenance_announcement() %}
<div class="maintenance-announcement">Maintenance planned on {{ maintenance_announcement()|date('d/m/Y H:i') }}</div>
{% endif %}
6. Reacting to Maintenance Changes
The bundle dispatches an event whenever the maintenance is activated, deactivated or announced (commands or MaintenanceManager). endsAt and startsAt are integer Unix timestamps. A maintenance ending by itself after --duration dispatches no event:
MathisVincent\MaintenanceBundle\Event\MaintenanceActivatedEvent(level,endsAtornull)MathisVincent\MaintenanceBundle\Event\MaintenanceDeactivatedEvent(level), only when a maintenance was activeMathisVincent\MaintenanceBundle\Event\MaintenanceAnnouncedEvent(startsAt)
#[AsEventListener] public function onActivated(MaintenanceActivatedEvent $event): void { // notify the team, clear a cache... }
7. Contributing
Contributions are welcome! Bug reports, feature ideas and pull requests can all be submitted on GitHub.
The whole toolchain runs in Docker, so you only need Docker and make:
make install # build the image and install dependencies make ci # coding style + static analysis + tests, like the CI
See CONTRIBUTING.md for the full workflow.
8. License
This bundle is released under the MIT License.