2026年9月11日

2026年9月11日

WordPressで長期間ログインのないユーザーを自動削除する方法(WP-Cron)

WordPress には、使われなくなったユーザーを期限で自動削除する機能はありません。また、本体は「最後にログインした日時」も保存していません。そのため自動削除するには、ログインのたびに日時を記録し、WP-Cron で毎日、記録が古いユーザーを探して削除する仕組みを自分で用意します。

この記事のコードは、2026年11月時点の WordPress 6.9 の検証環境で、古い日時を記録した購読者だけが削除され、管理者・別の権限グループ・最近ログインしたユーザーは残り、削除したユーザーの投稿が指定した管理者に引き継がれることを確かめています。

削除する前に決めておくこと

ユーザーの削除は元に戻せません。コードを入れる前に、次の点を決めておきます。

決めること 例と考え方
対象の権限グループ 会員登録で増える「購読者」だけにする。管理者・編集者・投稿者は対象にしない
期間 最後のログインから 365 日など。会員規約やプライバシーポリシーに書いた保存期間と合わせる
投稿の扱い 削除するユーザーの投稿を、指定したユーザーに引き継ぐ。引き継ぎ先を指定しないと投稿も削除される
事前の通知 削除前に知らせるなら、その仕組みも別に用意する(この記事のコードは通知しない)
バックアップ 導入前と、試運転を終えて本番に切り替える前にデータベースのバックアップを取る

権限グループごとにできることはWordPressのユーザー権限グループ一覧、削除時の引き継ぎはWordPressのユーザー削除時にコンテンツを他ユーザーに引き継ぐで説明しています。

手順1: コードを mu-plugins に置く

テーマを替えても動き続けるように、wp-content/mu-plugins/ フォルダーに置きます(mu-plugins は、管理画面で有効化しなくても常に読み込まれるプラグインの置き場所です。フォルダーが無ければ作ります)。ファイル名は delete-inactive-users.php とします。

<?php
/**
 * Plugin Name: Delete inactive users
 * Description: 最後のログインから一定日数が過ぎた購読者を毎日削除する。
 */

defined( 'ABSPATH' ) || exit;

// 設定: 何日ログインがなければ削除するか、対象の権限グループ、投稿の引き継ぎ先、試運転かどうか
const MYSITE_INACTIVE_DAYS     = 365;
const MYSITE_INACTIVE_ROLES    = array( 'subscriber' );
const MYSITE_REASSIGN_TO       = 1;     // 削除するユーザーの投稿を引き継ぐ管理者のユーザー ID
const MYSITE_INACTIVE_DRY_RUN  = true;  // true の間は削除せず、対象をログに書くだけ
const MYSITE_INACTIVE_BATCH    = 50;    // 1 回に処理する人数

// 1. ログインした日時を記録する
add_action( 'wp_login', 'mysite_record_last_login', 10, 2 );
function mysite_record_last_login( $user_login, $user ) {
	update_user_meta( $user->ID, 'mysite_last_login', time() );
}

// 2. 1 日 1 回の予定を登録する
add_action( 'init', 'mysite_schedule_inactive_cleanup' );
function mysite_schedule_inactive_cleanup() {
	if ( ! wp_next_scheduled( 'mysite_delete_inactive_users' ) ) {
		wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'mysite_delete_inactive_users' );
	}
}

// 3. 期限切れのユーザーを削除する
add_action( 'mysite_delete_inactive_users', 'mysite_delete_inactive_users' );
function mysite_delete_inactive_users() {
	require_once ABSPATH . 'wp-admin/includes/user.php'; // wp_delete_user() はここにある

	// 記録がまだ無いユーザーは、今日から数え始める(導入した直後に全員が対象になるのを防ぐ)
	$no_record = get_users(
		array(
			'role__in'   => MYSITE_INACTIVE_ROLES,
			'fields'     => 'ID',
			'number'     => 500,
			'meta_query' => array(
				array(
					'key'     => 'mysite_last_login',
					'compare' => 'NOT EXISTS',
				),
			),
		)
	);
	foreach ( $no_record as $user_id ) {
		update_user_meta( (int) $user_id, 'mysite_last_login', time() );
	}

	$limit   = time() - MYSITE_INACTIVE_DAYS * DAY_IN_SECONDS;
	$expired = get_users(
		array(
			'role__in'   => MYSITE_INACTIVE_ROLES,
			'fields'     => 'ID',
			'number'     => MYSITE_INACTIVE_BATCH,
			'meta_query' => array(
				array(
					'key'     => 'mysite_last_login',
					'value'   => $limit,
					'compare' => '<',
					'type'    => 'NUMERIC',
				),
			),
		)
	);

	foreach ( $expired as $user_id ) {
		$user_id = (int) $user_id;
		if ( user_can( $user_id, 'manage_options' ) ) {
			continue; // 念のため管理者は除く
		}
		if ( MYSITE_INACTIVE_DRY_RUN ) {
			error_log( 'inactive user (dry run): ' . $user_id );
			continue;
		}
		wp_delete_user( $user_id, MYSITE_REASSIGN_TO );
		error_log( 'inactive user deleted: ' . $user_id );
	}
}

コードの要点は次のとおりです。

  • 最終ログインの記録: wp_login アクションはログインに成功したときに実行されます。そこで現在時刻をユーザーメタ mysite_last_login に保存します。
  • 記録の無いユーザーの扱い: コードを入れた時点では、全員に記録がありません。記録の無いユーザーを「期限切れ」とみなすと、入れた直後に全員が削除されてしまうため、記録が無ければその日から数え始めます。つまり、導入から MYSITE_INACTIVE_DAYS 日が過ぎるまでは誰も削除されません。
  • 削除: wp_delete_user() は管理画面用のファイル(wp-admin/includes/user.php)にある関数なので、WP-Cron から呼ぶ前に読み込みます。2 番目の引数に指定したユーザー ID へ、削除するユーザーの投稿とリンクが引き継がれます。
  • 1 回の人数: 一度に大量に削除すると処理が重くなるので、1 日 50 人までにしています。残りは翌日以降に処理されます。

MYSITE_REASSIGN_TO の 1 は、実在する管理者のユーザー ID に変えてください。ユーザー ID は「ユーザー」→「ユーザー一覧」で名前にマウスを重ねたときのリンク(user_id= の後の数字)で確認できます。

手順2: 試運転で対象を確かめる

最初は MYSITE_INACTIVE_DRY_RUN を true のままにして、削除せずに対象者だけをログに書かせます。ログを wp-content/debug.log に出すには、wp-config.php の require_once ABSPATH . 'wp-settings.php'; より上に次の 3 行を書きます。

<?php
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false ); // エラーを画面には出さない

WP-CLI が使えるなら、予定を確認して、その場で 1 回実行できます。

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

# 今すぐ 1 回実行する
wp cron event run mysite_delete_inactive_users

# ログを確認する
tail -n 20 wp-content/debug.log

ログに出たユーザー ID が想定どおりかを、「ユーザー」の一覧と照らし合わせて確かめます。導入直後は全員の記録が「今日」になるので、対象は 0 人です。動きを早めに確かめたい場合は、検証環境で MYSITE_INACTIVE_DAYS を小さくするか、テスト用ユーザーに古い日時を入れて試します。

# 検証環境で、テスト用ユーザー(ID 25)の最終ログインを 400 日前にする
wp user meta update 25 mysite_last_login $(( $(date +%s) - 400*86400 ))

確認が終わったら MYSITE_INACTIVE_DRY_RUN を false に変え、wp-config.php の WP_DEBUG は元に戻します。

手順3: WP-Cron を決まった時刻に動かす

WP-Cron は、サイトにアクセスがあったときに予定の時刻を過ぎた処理を実行する仕組みです。アクセスの少ないサイトでは、実行が遅れることがあります。毎日決まった時刻に動かしたい場合は、wp-config.php で DISABLE_WP_CRON を true にし、サーバーの cron から wp-cron.php を定期的に呼び出す設定に切り替えます。手順はWordPressのWP-Cronを本番環境で最適化する方法を参照してください。予定が実行されないときはWordPressでcronジョブが実行されない場合の対処法で原因を確認できます。

自動にせず、手作業でまとめて削除する場合

定期的に自分で確認して消すなら、WP-CLI で一覧を出して削除できます。

# 購読者を登録日の古い順に一覧表示する
wp user list --role=subscriber --fields=ID,user_login,user_registered --orderby=user_registered --order=ASC

# ID 12 と 34 を削除し、投稿を ID 1 のユーザーに引き継ぐ
wp user delete 12 34 --reassign=1

管理画面で削除する場合は、「ユーザー」の一覧で対象にチェックを入れ、一括操作の「削除」を選びます。投稿を持つユーザーを削除するときは、投稿を削除するか、別のユーザーに引き継ぐかを選ぶ画面が表示されます。

注意点

  • マルチサイト: マルチサイトで wp_delete_user() を使うと、ユーザーはそのサイトから外れるだけで、ネットワークからは削除されません。ネットワーク全体から削除するには wpmu_delete_user() を使います。この記事のコードはシングルサイト向けです。
  • ログイン方法による違い: ソーシャルログインなど、WordPress の通常のログイン処理を通らない仕組みでは wp_login が実行されない場合があり、ログインしていても記録が更新されません。使っているログインのプラグインで、ログイン後に wp_login が実行されるかを確かめてください。
  • コードを外すとき: ファイルを削除しただけでは、登録した予定がデータベースに残ります。ファイルを削除した後に wp cron event delete mysite_delete_inactive_users で予定も消します。

関連記事

参考: wp_delete_user()(WordPress Developer Resources)、wp_schedule_event()、wp_login フック、wp cron event(WP-CLI)

お気軽にご相談ください

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