Camille Hodoul

Symfony Maintenance Mode

January 03, 2020

When you perform tasks on your application that could result in a broken layout or even errors displayed on the front-end, you want some kind of maintenance mode to show a waiting page to your users instead.

A brief survey of the suggested solutions online shows that often, these implementations assume the source code is in a stable state.

However if you have a corrupted cache, a missing vendor or permission issues on configuration files, a maintenance mode based on Symfony commands, Kernel event listeners and DI won’t help you and you will suffer errors.

Here is a way to safely implement maintenance mode to prevent all access to your front-end while your app is unstable, without having to mess with your nginx configuration.

Entrypoints

A Symfony app typically has 2 entrypoints:

  • public/index.php for HTTP Requests
  • bin/console for the CLI

Both need to be handled, as you could have cron jobs running while you’re doing maintenance work.

Edit those file like so:

bin/console:

/** At the top of the file */
if (is_readable(__DIR__.'/../0_MAINTENANCE')) {
    echo "Maintenance mode is enabled";
    exit(1);
}

exit(1) will return an error code.
The “flag” file is prefixed with 0_ in order to easily find it in a directory listing.

public/index.php:

/** At the top of the file */

if (is_readable(__DIR__.'/../0_MAINTENANCE')) {
    http_response_code(503);
    include "./maintenance.php";
    die();
}

HTTP 503 can be properly used in this case.
It is crucial that the responses you send while down for maintenance are NOT cached!

Add the following file:

public/maintenance.php:

if (!is_readable(__DIR__ . '/../0_MAINTENANCE')) {
    /**
     * Maintenance page used while not in maintenance mode
     */
    header("HTTP/1.1 301 Moved Permanently"); 
    /**
     * This should be changed according to your directory root
     */
    header("Location: /");
    die();
}
/** 
 * Then, include the actual HTML page 
 * you want to show your users 
 */

Toggle

A simple bash script can be used as a toggle

scripts/maintenance.sh:

#!/bin/bash

ACTION="$1"
MAINTENANCE_FILE=0_MAINTENANCE

if [ "$ACTION" == enable ];
then
    if [ -f "$MAINTENANCE_FILE" ];
    then
        echo "$MAINTENANCE_FILE already exists"
    else
        touch "$MAINTENANCE_FILE"
        chmod 744 "$MAINTENANCE_FILE"
    fi
elif [ "$ACTION" == disable ];
then
    if [ -f "$MAINTENANCE_FILE" ];
    then
        if [[ ! -w "$MAINTENANCE_FILE" ]]
        then
            echo "$MAINTENANCE_FILE can't be removed : check permissions"
            exit 1
        else
            rm "$MAINTENANCE_FILE"
        fi
    else
        echo "$MAINTENANCE_FILE does not exist"
    fi
else
    echo "Usage : \`scripts/maintenance.sh enable\`, \`maintenance.sh disable\`"
fi

Usage:
From the application root: scripts/maintenance.sh enable

Drawbacks

  • This solution won’t work if you need to make changes in bin/console or public/index.php. Luckily this shouldn’t happen very often.
  • If your app is distributed, you will need to create the file on every filesystem.
  • This will add a read on the filesystem for every execution of public/index.php or bin/console, which could have a performance cost.

Camille Hodoul

I'm a JavaScript and PHP developer living in Marseille, France.
Twitter, Github, Flickr