WPTip.me

WordPress Development Tutorials & Tips

WordPress Maintenance Mode Without a Plugin

 

When WordPress starts up, the first “point of entry” is your site’s index.php file. From there, wp-load.php gets included, followed by your wp-config.php, and finally wp-settings.php.

wp-settings.php is where most of WordPress’ includes happen as well as setting up the site and processing the request.

We’re going to focus on something from the very top of wp-settings.php, a function named wp_maintenance.

Here’s the source for that function:

/**
 * Dies with a maintenance message when conditions are met.
 *
 * Checks for a file in the WordPress root directory named ".maintenance".
 * This file will contain the variable $upgrading, set to the time the file
 * was created. If the file was created less than 10 minutes ago, WordPress
 * enters maintenance mode and displays a message.
 *
 * The default message can be replaced by using a drop-in (maintenance.php in
 * the wp-content directory).
 *
 * @access private
 * @since 3.0.0
 */
function wp_maintenance() {
	if ( !file_exists( ABSPATH . '.maintenance' ) || defined( 'WP_INSTALLING' ) )
		return;

	global $upgrading;

	include( ABSPATH . '.maintenance' );
	// If the $upgrading timestamp is older than 10 minutes, don't die.
	if ( ( time() - $upgrading ) >= 600 )
		return;

	if ( file_exists( WP_CONTENT_DIR . '/maintenance.php' ) ) {
		require_once( WP_CONTENT_DIR . '/maintenance.php' );
		die();
	}

	wp_load_translations_early();

	$protocol = $_SERVER["SERVER_PROTOCOL"];
	if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol )
		$protocol = 'HTTP/1.0';
	header( "$protocol 503 Service Unavailable", true, 503 );
	header( 'Content-Type: text/html; charset=utf-8' );
	header( 'Retry-After: 600' );
?>
	<!DOCTYPE html>
	<html xmlns="http://www.w3.org/1999/xhtml"<?php if ( is_rtl() ) echo ' dir="rtl"'; ?>>
	<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		

	</head>
	<body>
		<h1><?php _e( 'Briefly unavailable for scheduled maintenance. Check back in a minute.' ); ?></h1>
	</body>
	</html>
<?php
	die();
}

An Explanation

The above looks for a file in WordPress' root directory called .maintenance. If that file is found, WP includes it and then checks for a global $upgrading variable which contains a unix timestamp. If $upgrading is set to a timestamp sometime within the previous 10 minutes WordPress enters maintenance mode -- it shows a default (or your own custom) message to the user and dies.

Why is This Useful

When WordPress is updating a plugin or theme or itself it puts the site into maintenance mode using this special .maintenance file. The system will create the file with the content <?php $upgrading = time(); ?$gt;, which causes WordPress to show a maintenance message to the user.

Forcing your site into maintenance mode isn't very exiciting for the average user -- just let WordPress do it's upgrade thing and don't worry about it.

If you're using any sort of automated deployment tool, forcing the site into maintenance mode is awesome. Create the .maintenance file before doing any sort of upgrades, then do the upgrades, then remove the file. Your users never see errors due to in-progress file uploads or git merges and you send the correct status headers (503 along with a Retry-After header).

To give an example, I use Fabric along with git to deploy code for this site. WordPress never uses it's built in uploader. I can force the site into maintenance mode, then use git to pull in the latest changes.

def deploy(branch='stable'):
    code_d = '/path/to/doc/root'
    with settings(warn_only=True):
        if run('test -d %s' % code_d).failed:
            run('git clone --recursive wptip:chrisguitarguy/WPTip.me.git %s' % code_d)
    with cd(code_d):
        run("echo '<?php $upgrading = time(); ?>' > path/to/wp/.maintenance")
        run('git pull')
        run('git checkout %s' % branch)
        run('git submodule update --init --recursive')
        run('rm path/to/wp/.maintenance')

Displaying a Custom Error Message

The other part of this is that we can create our own custom error messages by using a drop-in. Drop-ins replace parts of WordPress' functionality when present. A custom object cache is one example. The drop to display a custom maintenance message is maintenance.php. If WordPress finds this file in your content directory (usual wp-content) it will include it instead of displaying its default error message.

The maintenance.php file for WPTip.me:

<?php
/**
 * Plugin Name: WPTip Maintenance Message
 * Description: Custom maintenance page.
 * Author: Christopher Davis
 * Author URI: http://christopherdavis.me
 * Version: 0.1
 *
 * Sends the correct headers so we don't scare of search bots, etc.
 */

!defined('ABSPATH') && exit;

$headers = array(
    'Content-Type'  => 'text/html; charset=utf-8',
    'Retry-After'   => '600',
    'Expires'       => 'Wed, 11 Jan 1984 05:00:00 GMT',
    'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT',
    'Cache-Control' => 'no-cache, must-revalidate, max-age=0',
    'Pragma'        => 'no-cache',
);

header("HTTP/1.1 503 Service Unavailable", true, 503);
foreach($headers as $h => $k)
    header("{$h}: {$k}", true);

?><!doctype html>
<html dir="ltr" lang="en-us">
<head>
    <title>Maintenance</title>

    <style>
        body {
            font-family: Ubuntu, Helvetica, Arial, sans-serif;
            color: #444;
            text-align: center;
        }

        .wrap {
            width: 600px;
            margin: 0 auto;
            padding: 5em 0 0 0;
        }

        .wrap > .pad {
            width: 95%;
            margin: 0 auto;
        }

        h1 {
            font-size: 2.5em;
            font-weight: normal;
            margin-bottom: 0.2em;
        }
    </style>
</head>
<body>
    <div class="wrap">
        <div class="pad">
            <h1>Maintenance Mode</h1>
            <p>Just making things better.  Check back in a few.</p>
        </div>
    </div>
</body>
</html>