Maintenance Mode Snippet Generator

Put a site into maintenance mode with a real 503 and Retry-After header, while letting logged-in users, an IP allowlist and the login page through.

Live output

Enable JavaScript to customise; default output below.

Turned on by

A constant in wp-config.php cannot be toggled by a plugin or a compromised admin account.

Live preview maintenance-mode.php
<?php
/**
 * Maintenance mode.
 *
 * Sends 503 with a Retry-After header rather than a 200 page saying "back
 * soon": a 200 tells search engines the maintenance page is the page.
 */

/**
 * Decides whether this request should see the site.
 *
 * @return bool
 */
function my_site_maintenance_bypass() {
	if ( is_user_logged_in() ) {
		return true;
	}

	if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
		return true;
	}

	return false;
}

/**
 * Shows the maintenance page to everyone else.
 */
function my_site_maintenance_mode() {
	if ( ! defined( 'MY_SITE_MAINTENANCE' ) || ! MY_SITE_MAINTENANCE ) {
		return;
	}

	// The login screen and admin-ajax stay up: locking yourself out of the
	// login page is how a ten minute maintenance window becomes an afternoon.
	if ( is_admin() || wp_doing_ajax() ) {
		return;
	}

	global $pagenow;

	if ( 'wp-login.php' === $pagenow ) {
		return;
	}

	if ( my_site_maintenance_bypass() ) {
		return;
	}

	header( 'Retry-After: ' . ( 60 * MINUTE_IN_SECONDS ) );

	wp_die(
		esc_html__( 'We are making a few changes and will be back within the hour.', 'my-site' ),
		esc_html__( 'Back shortly', 'my-site' ),
		array(
			'response'  => 503,
			'back_link' => false,
		)
	);
}
add_action( 'template_redirect', 'my_site_maintenance_mode' );

Output is valid and updates as you type.

Put the site behind a maintenance page without losing your search rankings or locking yourself out. The generated snippet sends a real 503 with a Retry-After header, and leaves the login page up.

How to use

  1. Switch it on with a constant in wp-config.php. A constant cannot be flipped by a plugin, or by someone who got into the admin.
  2. Set Retry-After to an honest estimate. It is what tells search engines to come back rather than to drop the page.
  3. Decide who gets through. Logged-in users is the convenient answer; a capability is the right one on a site with customer accounts.
  4. Leave WP-Cron running unless you have a reason not to. Scheduled jobs that pile up for an hour all fire at once when you come back.
  5. Test it in a private window before you announce anything. A maintenance page that is showing to you and nobody else is the usual outcome.

Example

The check that decides who sees the site:

function acme_maintenance_bypass() {
	if ( is_user_logged_in() ) {
		return true;
	}

	if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
		return true;
	}

	return false;
}

The wp-login.php exception in the generated code matters more than it looks: without it, an unlucky logout during maintenance means the only way back in is over SSH.

Pitfalls

  • A maintenance page served with a 200 status tells search engines this is the page. Repeated over days, the real page drops out of the index. 503 is the correct status.
  • Without Retry-After, a crawler decides for itself when to return, which can be much later than you would like.
  • Blocking wp-login.php locks you out the moment your session expires.
  • An IP allowlist matches REMOTE_ADDR, which behind Cloudflare or any proxy is the proxy’s address. The allowlist then matches nobody, or everybody.
  • template_redirect does not run for the REST API or for direct file requests, so a headless front end may keep working whether you want it to or not.
  • Page caching serves the maintenance page after you turn it off, and serves the real page during. Purge the cache at both ends.
  • wp_die() output is not a themed page. If the maintenance page has to look like the brand, render a template instead.
  • Leaving maintenance mode on for weeks is a real ranking risk. Google treats a long 503 as a site that is gone.

Compatibility

template_redirect, wp_die() with a response code, and is_user_logged_in() have been stable since WordPress 3.0. This is a code snippet, not WordPress’s own .maintenance file, which core creates during updates and which takes the whole site down including the admin. The generated code targets PHP 7.0 and up, and the tool runs entirely in your browser.

Frequently asked questions

Why 503 and not a normal page?
503 means “temporarily unavailable”. Search engines keep the page in the index and come back. A 200 tells them the maintenance notice is your content.
Will I be locked out?
Not through the login page: the snippet leaves wp-login.php and the admin up. Signing in restores the site for you.
How do I turn it on and off?
Add or remove the constant in wp-config.php, or flip the option if you chose that mode.
Does this work with a caching plugin?
Only if you purge the cache when you turn it on and again when you turn it off. Cached pages bypass PHP entirely.
Can I show a designed page instead?
Yes. Replace the wp_die() call with your own template and keep the status code and the header.

From the people who built this tool

WP Adminify

The WordPress admin, rebuilt: a dashboard worth looking at, menu and column control, a real file manager and the login page your client sees.

See WP Adminify Free version on WordPress.org

Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.