2026年6月28日

2026年6月28日

WordPressのヘルスチェック用エンドポイントを作り死活監視する方法|REST API・UptimeRobot・Slack通知

サイトのトップページが200で返っていても、アップロード先に書き込めない、オブジェクトキャッシュ(Redisなど)が応答しないといった、一部の機能だけが壊れていることがあります。この記事では、WordPressのREST APIに「ヘルスチェック用のエンドポイント」を追加して主要な部品を検査し、外部の監視サービスとWordPress内部の定期実行の両方から異常に気づけるようにします。

コードは、2026年10月時点のWordPress 7.1.3・PHP 8.3のテスト環境で動作を確認しています。

全体の構成

部品 役割
/wp-json/wpdr/v1/health データベース・アップロード先・オブジェクトキャッシュを検査し、すべて正常なら200、異常があれば503を返す
トークン wp-config.php に書いた文字列と一致するリクエストだけに応答する
外部の監視(UptimeRobotなど) 数分ごとにエンドポイントを呼び、200以外ならメールなどで知らせる
WP-Cronの自己チェック 5分ごとに同じ検査を行い、異常になったときと回復したときにSlackかメールで知らせる

WordPressが起動できない障害(PHPのエラー、データベースに接続できないなど)では、WP-Cronも動きません。そのため、通知の柱は外部の監視で、WP-Cronの自己チェックは補助と考えてください。データベースに接続できない場合、WordPressはエンドポイントの処理に入る前にエラーのページ(ステータス500)を返すため、外部の監視で検知できます。

手順1:エンドポイントのコードを置く

次のコードを wp-content/mu-plugins/wpdr-health-check.php として保存します(mu-plugins ディレクトリが無ければ作成します)。mu-pluginsに置いたファイルは、有効化の操作なしで常に読み込まれます。

<?php
/**
 * Plugin Name: WPDR Health Check
 * Description: /wp-json/wpdr/v1/health でデータベース・アップロード先・オブジェクトキャッシュを検査し、異常時は 503 を返す。WP-Cron で同じ検査を行い、異常と回復を通知する。
 */

/**
 * 各項目を検査して結果を返す。
 *
 * @return array{status: string, checks: array<string, array{status: string, message: string}>}
 */
function wpdr_health_run_checks(): array {
	global $wpdb;
	$checks = array();

	// 1. データベース
	$db_ok                = ( '1' === (string) $wpdb->get_var( 'SELECT 1' ) );
	$checks['database'] = array(
		'status'  => $db_ok ? 'pass' : 'fail',
		'message' => $db_ok ? 'connected' : 'query failed',
	);

	// 2. アップロード先に書き込めるか(一時ファイルを作って消す)
	$upload    = wp_upload_dir( null, false );
	$test_file = trailingslashit( $upload['basedir'] ) . '.wpdr-health-' . wp_generate_password( 8, false );
	$fs_ok     = ( false !== @file_put_contents( $test_file, 'ok' ) ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
	if ( $fs_ok ) {
		wp_delete_file( $test_file );
	}
	$checks['uploads'] = array(
		'status'  => $fs_ok ? 'pass' : 'fail',
		'message' => $fs_ok ? 'writable' : 'not writable',
	);

	// 3. 永続オブジェクトキャッシュ(Redis・Memcached など)を使っている場合だけ、書いて読み戻す
	if ( wp_using_ext_object_cache() ) {
		$value    = wp_generate_password( 12, false );
		$cache_ok = wp_cache_set( 'probe', $value, 'wpdr_health', 60 ) && wp_cache_get( 'probe', 'wpdr_health' ) === $value;
		$checks['object_cache'] = array(
			'status'  => $cache_ok ? 'pass' : 'fail',
			'message' => $cache_ok ? 'read/write ok' : 'read/write failed',
		);
	}

	$status = 'pass';
	foreach ( $checks as $check ) {
		if ( 'fail' === $check['status'] ) {
			$status = 'fail';
			break;
		}
	}

	return array(
		'status' => $status,
		'checks' => $checks,
	);
}

/**
 * REST API のエンドポイント /wp-json/wpdr/v1/health を登録する。
 */
add_action(
	'rest_api_init',
	function (): void {
		register_rest_route(
			'wpdr/v1',
			'/health',
			array(
				'methods'             => 'GET',
				'callback'            => 'wpdr_health_rest_response',
				'permission_callback' => 'wpdr_health_rest_permission',
			)
		);
	}
);

/**
 * トークンが wp-config.php の WPDR_HEALTH_TOKEN と一致するときだけ許可する。
 * トークンは X-Health-Token ヘッダーか ?token= で受け取る。
 */
function wpdr_health_rest_permission( WP_REST_Request $request ): bool {
	$expected = defined( 'WPDR_HEALTH_TOKEN' ) ? (string) WPDR_HEALTH_TOKEN : '';
	if ( strlen( $expected ) < 32 ) {
		return false; // トークンが未設定・短すぎる場合は誰にも応答しない
	}
	$given = $request->get_header( 'x_health_token' );
	if ( null === $given ) {
		$given = $request->get_param( 'token' );
	}
	return is_string( $given ) && hash_equals( $expected, $given );
}

function wpdr_health_rest_response(): WP_REST_Response {
	$result   = wpdr_health_run_checks();
	$response = new WP_REST_Response(
		array(
			'status'    => $result['status'],
			'checks'    => $result['checks'],
			'timestamp' => gmdate( 'c' ),
		),
		'pass' === $result['status'] ? 200 : 503
	);
	$response->header( 'Cache-Control', 'no-store' );
	return $response;
}

/**
 * WP-Cron で5分ごとに同じ検査を行い、状態が変わったときだけ通知する。
 */
add_filter(
	'cron_schedules',
	function ( array $schedules ): array {
		$schedules['wpdr_five_minutes'] = array(
			'interval' => 5 * MINUTE_IN_SECONDS,
			'display'  => 'Every 5 minutes (WPDR health)',
		);
		return $schedules;
	}
);

add_action(
	'init',
	function (): void {
		if ( ! wp_next_scheduled( 'wpdr_health_self_check' ) ) {
			wp_schedule_event( time(), 'wpdr_five_minutes', 'wpdr_health_self_check' );
		}
	}
);

add_action(
	'wpdr_health_self_check',
	function (): void {
		$result   = wpdr_health_run_checks();
		$was_fail = (bool) get_option( 'wpdr_health_failing', false );

		if ( 'fail' === $result['status'] && ! $was_fail ) {
			$failed = array_keys( wp_list_filter( $result['checks'], array( 'status' => 'fail' ) ) );
			wpdr_health_notify( '異常を検知しました: ' . implode( ', ', $failed ) );
			update_option( 'wpdr_health_failing', 1, false );
		} elseif ( 'pass' === $result['status'] && $was_fail ) {
			wpdr_health_notify( '回復しました' );
			delete_option( 'wpdr_health_failing' );
		}
	}
);

/**
 * Slack の Incoming Webhook に送る。URL が未設定なら管理者メールに送る。
 */
function wpdr_health_notify( string $message ): void {
	$text = sprintf( '[%s] %s (%s)', home_url(), $message, wp_date( 'Y-m-d H:i:s' ) );

	if ( defined( 'WPDR_SLACK_WEBHOOK_URL' ) && WPDR_SLACK_WEBHOOK_URL ) {
		wp_remote_post(
			WPDR_SLACK_WEBHOOK_URL,
			array(
				'headers' => array( 'Content-Type' => 'application/json' ),
				'body'    => wp_json_encode( array( 'text' => $text ) ),
				'timeout' => 5,
			)
		);
		return;
	}

	wp_mail( get_option( 'admin_email' ), 'WordPress ヘルスチェック', $text );
}

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

  • 検査の結果は、項目ごとに pass(正常)か fail(異常)で返します。1つでも fail があれば、全体も fail になり、ステータス503を返します
  • オブジェクトキャッシュの検査は、Redis Object Cacheなどの永続オブジェクトキャッシュを使っている場合だけ行います。使っていないサイトでは項目自体が出ません
  • 応答に Cache-Control: no-store を付け、ページキャッシュやCDNに結果を保存させないようにしています
  • 通知は、状態が「正常→異常」「異常→正常」に変わったときだけ送ります。異常が続いている間、5分ごとに通知が届くことはありません

手順2:wp-config.phpにトークンと通知先を書く

トークンは推測されない長い文字列にします。サーバーで次のコマンドを実行すると、64文字のランダムな文字列が得られます。

openssl rand -hex 32

wp-config.php の require_once ABSPATH . 'wp-settings.php'; の行より上に、次を追加します。

// ヘルスチェックのトークン(openssl rand -hex 32 で作った値に置き換える)
define( 'WPDR_HEALTH_TOKEN', 'ここに64文字のランダムな文字列' );

// Slack の Incoming Webhook の URL(使わない場合はこの行を書かない。管理者メールに送られる)
define( 'WPDR_SLACK_WEBHOOK_URL', 'https://hooks.slack.com/services/XXXX/XXXX/XXXX' );

SlackのIncoming WebhookのURLは、Slackアプリの設定でIncoming Webhooksを有効にし、通知先のチャンネルを選ぶと発行されます。URLを知っていれば誰でもそのチャンネルに投稿できるため、外部に漏らさないでください。

手順3:エンドポイントの応答を確かめる

curl で呼び出して、ステータスコードと応答を確かめます(TOKEN は手順2の値に置き換えます)。

# トークンをヘッダーで渡す
curl -s -w '\n%{http_code}\n' -H 'X-Health-Token: TOKEN' https://example.com/wp-json/wpdr/v1/health

# トークンなし(401 になれば、トークンの確認が働いている)
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/wp-json/wpdr/v1/health

正常な場合は、次のような応答とステータス200が返ります。

{"status":"pass","checks":{"database":{"status":"pass","message":"connected"},"uploads":{"status":"pass","message":"writable"}},"timestamp":"2026-10-29T16:06:29+00:00"}

アップロード先に書き込めない状態では、uploads が fail になり、ステータス503が返ります。

パーマリンク設定が「基本」(?p=123 の形式)の場合、/wp-json/ のURLは使えません。代わりに https://example.com/?rest_route=/wpdr/v1/health を使います。

手順4:UptimeRobotで外から監視する

UptimeRobotで、エンドポイントを定期的に呼び出すモニターを作ります。手順はUptimeRobotのヘルプにある流れのとおりです。

  1. ダッシュボードで「+ Add New Monitor」をクリックし、モニターの種類に「HTTP(s)」を選ぶ
  2. URLに https://example.com/wp-json/wpdr/v1/health?token=TOKEN を入力する
  3. 通知先(アラートの連絡先)と監視の間隔を選び、「Create monitor」で作成する

UptimeRobotのHTTP(s)モニターは、応答のステータスコードで正常・異常を判定します。このエンドポイントは異常時に503を返すため、ステータスコードだけで障害を検知できます。

  • 応答の本文に含まれる文字列で判定する「Keyword」モニターを使う場合、指定する語に注意します。たとえば healthy を「含まれていれば正常」の語にすると、unhealthy のような異常を表す語にも一致してしまいます。この記事のコードはステータスコードで結果を返すので、HTTP(s)モニターで足ります
  • トークンが違うと、エンドポイントはステータス401を返します。UptimeRobotは401を特別に扱う場合があるため、登録前に手順3で200が返ることを確かめ、URLのトークンを書き間違えないようにしてください
  • URLに含めたトークンは、Webサーバーのアクセスログや監視サービスの設定画面に残ります。監視専用の値にし、漏れた疑いがあれば WPDR_HEALTH_TOKEN を変更してください。監視サービスでリクエストヘッダーを設定できる場合は、X-Health-Token ヘッダーで渡すとURLに残りません

2026年10月時点のUptimeRobotのヘルプでは、無料プランの監視間隔は5分とされています。

手順5:WP-Cronの自己チェックを確かめる

コードを置くと、5分ごとの予定が登録されます。WP-CLIで登録を確かめ、手動で実行できます。

# 予定が登録されているか
wp cron event list --fields=hook,next_run_relative,recurrence | grep wpdr

# 今すぐ実行する(状態が変わったときだけ通知が送られる)
wp cron event run wpdr_health_self_check

WP-Cronは、サイトへのアクセスをきっかけに動く仕組みです。アクセスの少ないサイトでは予定どおりに動かないことがあるため、本番ではサーバーのcronからWP-Cronを呼び出す設定をおすすめします(WordPressのWP-Cronを本番環境で最適化する方法)。

注意点

  • REST APIを制限しているプラグイン:未ログインの利用者からのREST APIへのアクセスを止めるセキュリティプラグインを使っていると、エンドポイントが401や403を返し、監視が常に異常と判定します。その場合は、/wp-json/wpdr/v1/health を許可する設定を追加します
  • ページキャッシュ・CDN:Cache-Control: no-store を返していますが、CDNの設定で強制的にキャッシュしている場合は、このURLをキャッシュの対象から外します
  • 検査の負荷:1回の呼び出しで、データベースへの問い合わせ1回と小さなファイルの作成・削除を行います。1分より短い間隔で呼び出す必要はありません
  • 検査項目の追加:メール送信や外部APIなど、サイトに欠かせない機能があれば、wpdr_health_run_checks() に同じ形で項目を追加できます。外部サービスへの通信を伴う検査は、相手の障害でもサイトが「異常」になる点に注意してください

まとめ

  • REST APIに、主要な部品を検査して200か503を返すエンドポイントを追加し、トークンで保護する
  • 外部の監視(UptimeRobotのHTTP(s)モニター)で、ステータスコードを基準に障害を検知する
  • WP-Cronの自己チェックは、状態が変わったときだけSlackやメールに通知する補助として使う
  • 文字列で判定する場合は、healthy と unhealthy のように別の値に含まれる語を避ける

関連記事:

お気軽にご相談ください

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