予約投稿にした記事が、時刻を過ぎても公開されていない。バックアップのプラグインが「最後の実行:3日前」で止まっている。期限が近いページのリマインドが、届くはずの日に来ない。症状はばらばらに見えますが、原因が1か所に集まることがあります。WordPressの定期処理、WP-Cronです。
そしてこれは、時計で動く仕組みではありません。誰かがそのサイトを開いた瞬間に、そのついでに走る。だから人が来ないサイトでは、予定した時刻が来ても何も起きない。この記事では、その前提と、止まっていると疑ったときに何をどの順で見るか、直す方向までを書きます。サーバーごとのcron設定画面の話には踏み込みません。名前も書式も各社で違うので、そこは契約先のマニュアルのほうが正確です。
症状はばらばらでも、止まっているのは同じ1本
WP-Cronの上に乗っているものを並べると、こうなります。
- 予約投稿の公開(WordPressコアの機能)
- 自動更新のチェック、期限切れトランジェントの掃除といったコアの内部処理
- バックアップ・外部サービス連携・SEO系プラグインの定期実行
- 通知やリマインドをメール・チャットへ送るプラグイン
これらは別々の機能ですが、実行の順番待ちをしている行列は共通です。行列が進まなければ、全部が同時に静かになる。予約投稿の一覧に「予約投稿の失敗」と出るのはその代表例で、投稿の日時設定が壊れたわけではなく、公開する処理そのものが呼ばれていないだけ。
厄介なのは、どれもエラーを出さないことです。画面は普通に表示され、ログにも残らず、「そういえば来ていない」と人が気づくまで止まったまま。
WP-Cronは時計ではなく、アクセスで動く
予定は wp_options テーブルの cron という項目に、「この時刻に、この処理を」という配列で入っています。誰かがサイトのページを開くと、WordPressは読み込みの途中でその配列を見て、予定時刻を過ぎたものがあれば wp-cron.php に向けて自分自身へHTTPリクエストを投げ、そちら側で実行する。このリクエストは応答を待たずに投げるので、開いた人のページ表示がそのぶん待たされることはまずありません。
つまり、定期処理が動くには3つ揃っている必要があります。予定が登録されていること。誰かがアクセスすること。そのサイトが自分自身にHTTPリクエストを送れること。どれが欠けても、黙って止まる。
短時間にアクセスが集中しても多重には走りません。実行中は鍵がかかり、既定では60秒のあいだ次の起動を受け付けない。逆に言えば、アクセスが1日に数回しか無いサイトでは、その数回のうちのどれかが来るまで行列は1ミリも進まない。
「動かない」には3つの形がある

同じ「動かない」でも、中身が違います。
予定そのものが無い。 プラグインがイベントを登録できていない、あるいは何かの拍子に解除された状態。この場合はcronの問題ではないので、後述の環境まわりを触っても直りません。
予定はあるが発火していない。 アクセスが足りない、DISABLE_WP_CRON で自動起動が止めてある、サイトが自分自身を叩けない、のいずれか。件数が溜まっていくのが特徴です。
発火しているが、遅れている。 これを「動かない」と呼んでいるケースが混ざります。WP-Cronの予定時刻は「この時刻ちょうどに実行する」という約束ではなく、「この時刻以降、最初に誰かが来たとき」。9:00の予定が11:20に走るのは故障ではなく、仕様どおりの姿です。分単位の正確さが要る処理を、WP-Cronだけに任せてはいけない理由でもある。
見る順番:予定の一覧 → 設定ファイル → ループバック → アクセス量
切り分けは、上の3つのどれかを確定させる順に進めると早いです。
- 予定の一覧を見る。 WP-CLIが使えるなら
wp cron event listで、登録されているイベント名と次回実行予定が並びます。管理画面から一覧を見て手動実行できるプラグイン(WP Crontrolなど)でも構いません。目当ての処理名がそもそも無ければ、ここで打ち止め。プラグイン側の登録を疑います。 - 次回実行予定が過去のまま溜まっていないか見る。 過去日のイベントが積み上がっていれば、発火していないほうの故障です。ここまでで「cronの問題かどうか」は決まります。
wp-config.phpのDISABLE_WP_CRONを見る。trueならコアの自動起動は意図的に止められています。誰かが負荷対策で止めたのか、サーバー側の初期設定なのか。そして、止めた代わりのcronが組んであるのか。止めただけで代わりが無い状態は、切り分けで最も見つかりやすい原因です。- サイトが自分自身を叩けるか確かめる。 ステージング環境公開中のサイトと同じ内容で用意する確認用のコピー。プラグインの更新やデザイン変更を、本番のサイトに影響させずに試す場所。のBasic認証、IP制限、社内ネットワークからしか見えない設定。ブラウザでは普通に見えているのに、
wp-cron.phpへのリクエストだけが弾かれている、という状態があり得ます。サイトヘルスの「ループバックリクエスト」の項目が赤ければここ。 - アクセス量を見る。 公開前のサイト、社内向けのサイト、月に数十アクセスのサイト。この規模なら、半日ずれるのはむしろ普通のことです。
1と2を飛ばして3から入ると、cronは正常なのにサーバー設定をいじって時間を溶かすことになります。
直す方向は2つ、そのどちらでもない逃げ道が1つ

いちばん確実なのは、外から叩く形に変えることです。DISABLE_WP_CRON を有効にしてアクセス駆動をやめ、サーバー側の定期実行(cron)から wp cron event run --due-now を呼ぶ。期限が来ているイベントだけをその場で実行するコマンドで、アクセスの有無と無関係に走ります。WP-CLIが使えない契約なら、外部の定期アクセスサービスから wp-cron.php を叩く形になる。呼ぶ間隔は、載っている処理の粒度に合わせれば足ります。毎時の処理しか無いところへ1分ごとのcronを組んでも、増えるのは負荷だけ。
もう一方は、遅れることを受け入れる判断です。1日を通してぽつぽつとでもアクセスがあるサイトなら、放っておいても行列は進みます。ただし「毎朝9時ちょうどに届く通知」は諦めることになる。日単位で判定する処理なら誤差は問題になりませんが、時刻を約束する処理なら前者を選ぶべきです。
逃げ道として ALTERNATE_WP_CRON があります。訪問者のリクエストにリダイレクトあるURLを開いたとき、別のURLへ自動で転送すること。ページを消す代わりに関連ページへ送ると、検索エンジンからの流入やブックマークを失いにくい。を挟んで実行する方式で、ループバックが通らない環境では効きます。ただしURLに ?doing_wp_cron が付くことがあり、アクセスが無いサイトでは結局動きません。ループバックが塞がれているときの代替であって、アクセス不足の解決策ではない。
期限の通知も、この行列の上に乗っている
ここまでの前提は、更新期限を管理するプラグインを開発しているあいだ、そのまま設計条件でした。コンテンツ更新管理は毎時のWP-Cronイベント1本に、期限の通知、期限切れ処理、新しい確認サイクルの開始、失敗した通知の再送、履歴の刈り込みを載せています。別に日次のイベントが1本あって、こちらはACFAdvanced Custom Fields。投稿に独自の入力欄(カスタムフィールド)を追加するプラグインの定番。無料版と有料のPro版がある。のフィールド定義が消えていないかを見に行くだけ。cronが止まれば、その全部が黙って止まる。
なので、遅れることと何度も走ることを前提にしました。多重起動はロックで抑止し、遅れた分は次の実行で補完する。通り過ぎた期限前通知を遡って全部送りはせず、いま有効な最新段階だけを送ります。その前提で通知を何にどう流しているかはSlack・Chatwork・メールへの通知の記事、期限を過ぎた投稿に何が起きるかは処理の選び方をまとめた記事に書きました。
アクセスがほとんど無く、サーバーのcronも組めないサイトについては、向いていない環境として最初から挙げています。通知の仕組みを足す前に、その行列が進んでいるかどうかを先に確かめてください。どのページに期限を持たせ、誰に確認させるかという設計そのものは柱の記事に、製品ページは コンテンツ更新管理 にあります。
