2026年5月19日

2026年5月19日

WordPressのメンテナンスモードを実装する方法|.maintenance・503応答・IP制限

サイトのリニューアルや大きな更新の作業中に、訪問者にはメンテナンス中の案内を出し、管理者だけはサイトを確認できるようにしたいことがあります。WordPress には更新時に使われる標準のメンテナンスモードがありますが、10分で自動的に解除される仕組みのため、長い作業には向きません。

この記事では、標準の .maintenance ファイルの仕組みを確認したうえで、wp-config.php の定数で切り替えるメンテナンスモードを mu-plugin として実装する方法を説明します。IP アドレスによる許可は、偽装できるヘッダーを使わない方法で行います。内容は2026年10月時点の WordPress 6.9 のソースコードで確認しています。

標準のメンテナンスモード(.maintenance)の仕組み

WordPress は、ルートディレクトリ(wp-config.php と同じ場所)に .maintenance というファイルがあると、メンテナンスモードとして動きます。プラグインや本体の更新中に一時的に表示される、メンテナンス中の短い案内の画面(英語の原文は Briefly unavailable for scheduled maintenance. Check back in a minute.)がこれです。

  • ファイルの中では、変数 $upgrading に開始時刻(UNIX タイムスタンプ)を入れます。
  • $upgrading の時刻から10分以上たつと、ファイルが残っていても通常の表示に戻ります。
  • メンテナンス中は、管理画面を含むすべてのページが止まります。管理者も操作できません。
  • 応答は HTTP ステータス 503 で、Retry-After: 600 が付きます。

WP-CLI では、次のコマンドでこのファイルを作成・削除できます。

wp maintenance-mode activate    # .maintenance を作成して有効にする
wp maintenance-mode status      # 状態を表示する
wp maintenance-mode is-active   # 有効なら終了コード 0 を返す(スクリプト向け)
wp maintenance-mode deactivate  # 解除する

activate で作られるファイルにも作成時刻が書き込まれるため、10分たつと解除されたものとして扱われます。更新作業の途中で処理が止まり、.maintenance が残ってサイトが表示されないときは、このファイルを削除すれば元に戻ります。

表示する画面を変える(wp-content/maintenance.php)

wp-content/maintenance.php を置くと、標準の画面の代わりにこのファイルが読み込まれます。ただし、このファイルは WordPress の関数が読み込まれる前に実行されるため、bloginfo() や esc_html() などの WordPress の関数は使えません。ステータスコードも自分で返します。

<?php
// wp-content/maintenance.php(WordPress の関数は使えない)
http_response_code( 503 );
header( 'Retry-After: 600' );
header( 'Content-Type: text/html; charset=UTF-8' );
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>メンテナンス中</title>
</head>
<body>
<h1>メンテナンス中です</h1>
<p>現在、サイトの更新作業を行っています。しばらくしてから再度アクセスしてください。</p>
</body>
</html>

.maintenance は「更新の数分間だけ止める」ための仕組みです。作業に時間がかかる場合や、管理者が作業中にサイトを見たい場合は、次の方法で別に作ります。

wp-config.php の定数で切り替えるメンテナンスモード

ここでは、次の動きをするメンテナンスモードを mu-plugin として作ります。

対象 動き
一般の訪問者 503 とメンテナンスの案内を返す
ログイン中の管理者(manage_options 権限) 通常どおり表示する
許可した IP アドレス ログインしていなくても通常どおり表示する
管理画面・ログイン画面・WP-Cron・WP-CLI 止めない

1. wp-config.php に定数を書く

wp-config.php の「編集が必要なのはここまでです」(英語版では That's all, stop editing!)という行より上に、次の定数を追加します。接頭辞 MYSITE_ は、他のプラグインと名前が重ならないよう自分のものに変えてかまいません。

// メンテナンスモードを有効にする(終わったら false にするか行を消す)
define( 'MYSITE_MAINTENANCE', true );

// ログインしていなくても通す IP アドレス(カンマ区切り。任意)
define( 'MYSITE_MAINTENANCE_IPS', '203.0.113.10,2001:db8::10' );

// 期間を決める場合(任意)。タイムゾーンを付けて書く
define( 'MYSITE_MAINTENANCE_START', '2026-11-15 02:00:00 +09:00' );
define( 'MYSITE_MAINTENANCE_END', '2026-11-15 04:00:00 +09:00' );

MYSITE_MAINTENANCE_START と MYSITE_MAINTENANCE_END を書くと、その間だけメンテナンス画面が出ます。WP-Cron でオン・オフを切り替える方法と違い、アクセスの有無に左右されずに時刻どおりに切り替わります。

2. mu-plugin を置く

wp-content/mu-plugins/ フォルダー(無ければ作る)に、mysite-maintenance.php という名前で次のファイルを置きます。mu-plugins に置いたファイルは自動で読み込まれ、管理画面から無効化されることもありません。

<?php
/**
 * Plugin Name: Mysite Maintenance Mode
 * Description: wp-config.php の定数 MYSITE_MAINTENANCE でメンテナンスモードを切り替える
 */

add_action( 'wp_loaded', 'mysite_maintenance_mode' );

function mysite_maintenance_mode() {
	if ( ! defined( 'MYSITE_MAINTENANCE' ) || ! MYSITE_MAINTENANCE ) {
		return;
	}

	// 期間の指定があれば、その間だけ有効にする
	$now   = time();
	$start = defined( 'MYSITE_MAINTENANCE_START' ) ? strtotime( MYSITE_MAINTENANCE_START ) : false;
	$end   = defined( 'MYSITE_MAINTENANCE_END' ) ? strtotime( MYSITE_MAINTENANCE_END ) : false;
	if ( ( $start && $now < $start ) || ( $end && $now >= $end ) ) {
		return;
	}

	// 管理画面(admin-ajax.php を含む)、WP-Cron、WP-CLI は止めない
	if ( is_admin() || wp_doing_cron() || ( defined( 'WP_CLI' ) && WP_CLI ) ) {
		return;
	}

	// ログイン画面は止めない(止めると管理者がログインできなくなる)
	if ( isset( $GLOBALS['pagenow'] ) && 'wp-login.php' === $GLOBALS['pagenow'] ) {
		return;
	}

	// ログイン中の管理者は通す
	if ( current_user_can( 'manage_options' ) ) {
		return;
	}

	// 許可した IP アドレスは通す。判定には REMOTE_ADDR だけを使う
	if ( defined( 'MYSITE_MAINTENANCE_IPS' ) && isset( $_SERVER['REMOTE_ADDR'] ) ) {
		$allowed = array_filter( array_map( 'trim', explode( ',', MYSITE_MAINTENANCE_IPS ) ) );
		if ( in_array( $_SERVER['REMOTE_ADDR'], $allowed, true ) ) {
			return;
		}
	}

	// 終了時刻が分かっていれば、それまでの秒数を Retry-After に入れる
	$retry_after = ( $end && $end > $now ) ? $end - $now : 3600;

	status_header( 503 );
	nocache_headers();
	header( 'Retry-After: ' . $retry_after );
	header( 'Content-Type: text/html; charset=UTF-8' );

	// テーマに maintenance-page.php があれば、それを表示に使う
	$template = get_stylesheet_directory() . '/maintenance-page.php';
	if ( file_exists( $template ) ) {
		include $template;
		exit;
	}
	?>
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex">
<title><?php echo esc_html( 'メンテナンス中 | ' . get_bloginfo( 'name' ) ); ?></title>
</head>
<body>
<h1>メンテナンス中です</h1>
<p>現在、サイトの更新作業を行っています。しばらくしてから再度アクセスしてください。</p>
	<?php if ( $end ) : ?>
<p>終了予定: <?php echo esc_html( wp_date( 'Y年n月j日 H:i', $end ) ); ?></p>
	<?php endif; ?>
</body>
</html>
	<?php
	exit;
}

ポイントは次のとおりです。

  • wp_loaded はフィルターではなくアクションです。WordPress・プラグイン・テーマの読み込みが終わった時点で実行されるため、current_user_can() でログイン中のユーザーの権限を判定できます。
  • is_admin() は管理画面のリクエストで真になります。ログインしていない訪問者が /wp-admin/ を開いた場合は、通常どおりログイン画面へ転送されます。
  • ログイン画面のURLを変えるプラグインを使っている場合は、wp-login.php の判定だけでは通らないことがあります。作業前に、ログアウトした状態でログインできるかを確かめてください。
  • nocache_headers() で、メンテナンス画面がブラウザーやプロキシにキャッシュされないようにしています。
  • 表示を整えたい場合は、子テーマに maintenance-page.php を作ります。この時点では WordPress の関数が使えるので、get_bloginfo() や esc_html() も使えます。

IP アドレスでの許可は REMOTE_ADDR で判定する

IP アドレスで許可を出すとき、X-Forwarded-For や CF-Connecting-IP などのリクエストヘッダーを読んではいけません。これらのヘッダーは、アクセスする側が自由に付けられます。たとえば次のように送れば、ヘッダーをそのまま信じるコードでは、許可した IP アドレスになりすませます。

curl -H "X-Forwarded-For: 203.0.113.10" https://example.com/

$_SERVER['REMOTE_ADDR'] は、Web サーバーに実際に接続してきた相手のアドレスなので、ヘッダーで書き換えられません。

Cloudflare などの CDN やロードバランサーを通している場合、REMOTE_ADDR は CDN 側のアドレスになります。その場合は PHP でヘッダーを読むのではなく、Web サーバーの機能で「信頼するプロキシから来た場合だけ」元の IP アドレスに置き換えます。

  • Nginx: ngx_http_realip_module の set_real_ip_from(信頼するプロキシのアドレス範囲)と real_ip_header
  • Apache 2.4: mod_remoteip の RemoteIPTrustedProxy と RemoteIPHeader

こうしておくと、PHP には置き換え後の REMOTE_ADDR が渡るので、上の mu-plugin はそのまま使えます。Cloudflare を使う場合の具体的な設定は、関連記事の Cloudflare WAF の記事で説明しています。

IPv6 のアドレスは、同じアドレスでも省略の仕方で表記が変わることがあります。アクセスログに記録されている表記をそのまま MYSITE_MAINTENANCE_IPS に書いてください。

動作を確かめる

ログアウトした状態(またはブラウザーのプライベートウィンドウ)で、ステータスコードとヘッダーを確認します。

curl -sI https://example.com/ | grep -iE "^HTTP|retry-after|cache-control"
# HTTP/2 503、retry-after、cache-control: no-cache, must-revalidate, max-age=0, ... が出れば有効

あわせて、次の点を確認します。

  1. 管理者でログインすると、通常のページが表示される。
  2. https://example.com/wp-login.php が開き、ログインできる。
  3. 許可した IP アドレスから、ログインせずに通常のページが表示される。

キャッシュと検索エンジンへの影響

  • ページキャッシュ: キャッシュ系プラグイン、サーバーのキャッシュ、CDN のキャッシュは、WordPress より手前で古いページを返すことがあります。メンテナンスモードの切り替え前後に、それぞれのキャッシュを削除してください。
  • 503 を返す理由: メンテナンス画面を 200 で返すと、検索エンジンがメンテナンスの案内をページの内容として扱うおそれがあります。Google は 5xx のステータスを受け取ると、その内容を無視し、クロールの頻度を一時的に下げます。
  • 長期間のメンテナンス: Google のドキュメントでは、サーバーエラーを返し続ける URL はいずれインデックスから外されるとしています。数時間で終わる作業に使い、長く止める場合は、主要なページだけでも表示できる方法を検討してください。

メンテナンスを終えるとき

  1. wp-config.php の MYSITE_MAINTENANCE を false にする(または行を消す)。
  2. ページキャッシュと CDN のキャッシュを削除する。
  3. ログアウトした状態でステータスが 200 に戻ったことを確認する。

mu-plugin のファイルは、定数が無ければ何もしないので、次回のために残しておいてもかまいません。

まとめ

  • 標準の .maintenance は作成から10分で解除される仕組みで、管理画面も止まる。更新作業の数分間向け。
  • wp-content/maintenance.php では WordPress の関数が使えないため、503 も自分で返す。
  • 長い作業には、wp-config.php の定数と wp_loaded アクションで動く mu-plugin を使い、管理画面・ログイン画面・管理者を通す。
  • IP アドレスの判定は REMOTE_ADDR で行い、CDN の背後では Web サーバーの realip / remoteip の設定で元の IP アドレスに置き換える。
  • 503 と Retry-After を返し、切り替えの前後でキャッシュを削除する。

関連記事:

お気軽にご相談ください

お見積りへ お問い合わせへ
▲