夜中だけ予約メールが届かない|WP-Cron は「アクセスで動く」と知らずに半日溶かした話(Xserver + WP-CLI で安定運用)

WordPress
この記事は約20分で読めます。

2026年1月のある朝、コーヒーを淹れる前にメールを開いたら、夜のあいだに送られているはずのテストメールが、1通も届いていませんでした。昼間に予約したぶんは、いつも通り来ている。深夜に予約したものだけ、朝になっても来ていない。そして、私がサイトを開いたちょうどその時刻あたりに、止まっていたぶんが、まとめて流れ込んできました。

raplsworks.com で開発を続けている Thanks Mail for Stripe Custom というカスタマイズ版プラグインを検証していたときの話です。半日かけて原因を追って、たどり着いたのは、メールでも自分のコードでもなく、WP-Cron という仕組みの思い込みでした。WP-Cron は、時刻になったら自動で動くものではなく、誰かがアクセスした瞬間に動くものだった。月間 PV が 1,500 ほどの個人ブログで、深夜はほとんどアクセスがない。だから動かなかったのではなく、そもそも呼ばれていなかったわけです。

この記事は二部構成です。前半は、夜中のメールが止まった症状から原因にたどり着き、Xserver の cron で wget を使って一度直すまでの、失敗まじりの記録。後半は、そこで出てきた WP Rocket の警告をきっかけに、WP-CLI 構成へ切り替えて安定運用へ持ち込んだ、学びの記録です。設定の羅列ではなく、迷ったところと軌道修正をそのまま残しました。

この記事の前提

Xserver スタンダードプランの共用レンタル上で動いている、月間 PV 1,500 程度の個人ブログ(raplsworks.com)での体験記です。VPS、専用サーバー、別のレンタルサーバー、もっと規模の大きなサイトでは、また違ってくる可能性があります。

記事に貼っているコードや設定値は、私の環境で動いた最小サンプルです。本番運用に持ち込むときは、サーバーの設定や WordPress のバージョンに合わせて調整してください。

検証環境:WordPress 6.8.3 / Xserver スタンダード / PHP 8.3.21 / WP Rocket
検証実施:2026年1月 / 記事更新:2026年5月5日

確認日と検証範囲

確認日: 2026年1月(問題発見・対応・検証)、2026年5月5日(本記事の最終確認)
確認できたこと: 自分のサイト raplsworks.com(月間 PV 1,500 程度)/ Xserver スタンダードプラン / WordPress 6.8.3 / PHP 8.3.21 / WP Rocket での挙動。wget 構成での「Scheduled events failed to run」エラーの再現、WP-CLI 構成への切り替えによる解消、テストメールの 5 分以内到着、WP Rocket ダッシュボードでのエラー消失
確認できていないこと: 他のレンタルサーバー(ConoHa WING、ロリポップ!、さくらインターネット等)での同等構成の挙動、WP Rocket 以外のキャッシュプラグイン(W3 Total Cache、LiteSpeed Cache 等)での再現性、月間 PV が 10 万を超えるような中・大規模サイトでの 5 分間隔の負荷、Xserver の上位プラン(プレミアム / ビジネス)での挙動の違い、WordPress 6.9 以降での挙動
備考: 本記事の数値・コマンド・手順は、すべて私の環境(Xserver スタンダード / WordPress 6.8.3)での実測結果です。サーバーの仕様や WordPress のバージョンによって最適解は変わります。本番に適用する前には必ず検証環境で動作を確認してください。

WP-Cron がアクセス依存で深夜に予約メールが動かない問題から、サーバー cron と WP-CLI による安定運用までの流れを示した図

症状→原因→wget→WP Rocket警告→WP-CLI→安定 の流れ

第1部・失敗|夜中だけ届かないメールと、WP-Cron の正体

テストメールは、何件かを時間指定で予約していました。Stripe の決済をきっかけにサンクスメールを送る WordPress.org 公式版の Thanks Mail for Stripe を、自サイト用にカスタマイズした Custom 版の検証で、テストの都合上、深夜ぶんも混ざっていました。届いていなかったのは、その深夜ぶんです。深夜2時に出るはずのメールが来ていない。3時のぶんも来ていない。代わりに、朝アクセスした直後の時刻に、まとめて何通か届いていました。

「夜中だけメールが届かない」症状の時系列イメージ図

最初に疑ったのは、いつも通り SMTP の設定でした。送信元ドメインの認証は通っているか、From は独自ドメインか、迷惑メールに落ちていないか。ひと通り見ましたが、昼間のテストは届くので、設定そのものは違いそうです。次に疑ったのは、自分が書いた Thanks Mail for Stripe Custom のコードのほうでした。スケジュールの時刻計算を取り違えていないか、タイムゾーンの扱いを間違えていないか、特定の時間帯だけ送信を止める分岐を、うっかり書いていないか。何度も読み直しましたが、怪しいところは出てきません。ここまで来て、ようやく、メールではなく、メールを送ろうとする処理そのものが呼ばれていないのでは、という疑いが立ち上がりました。半日ほど AI と壁打ちして、検索でも同じ症状の記事をいくつか見つけて、WP-Cron の仕様を調べ直す段階に入りました。症状とアクセス状況を並べると、重なりがはっきり見えます。

時間帯 アクセス状況 メール送信の状態
昼間(10:00〜18:00) アクセスがある ほぼ予定通り動く
夜(18:00〜24:00) アクセスが減る 少し遅れることがある
深夜〜早朝(0:00〜7:00) ほとんどアクセスがない 送信処理が動かない

昼は動く。夜は遅れる。深夜は止まる。メール送信の問題というより、アクセスの量に紐づいた問題でした。WordPress には WP-Cron という仕組みが入っていて、予約投稿、定期バックアップ、メール送信など、時間で動かしたい処理はだいたいこれが裏で支えています。名前に Cron と付くので、Linux の cron と同じく、時刻になったら自動で動くものだと、なんとなく思っていました。公式の Plugin Handbook を読み直すと、動き方は少し違います。誰かがページを開いた瞬間に、WordPress が、いま実行予定を過ぎているタスクはあるか、を確認して、あればその場で実行する。つまり、アクセスがチェックの引き金で、引き金が引かれなければ予定の確認すら走りません。

WP-Cron がアクセス起動でしか動かないことを示す概念図

アクセスが定期的に来るサイトでは、これは便利な仕組みです。サーバー側で何も足さなくても、予約投稿もプラグインの定期処理も、それなりに動く。共用レンタルで cron が使えない・面倒という制約があっても、最低限のスケジュールは WordPress 側で完結します。困るのは、アクセスが少ない時間帯でした。午前2時にメール送信を予約していても、その時刻に誰も来なければ、WordPress は予定を確認しに行きません。次のアクセスが来た瞬間に、ようやく、これ予定時刻を過ぎてる、と気づいて実行する。だから raplsworks.com では、深夜ぶんが朝までずれ込んで、私が朝見に来た瞬間にまとめて届いていたわけです。下の図は、デフォルトの WP-Cron(左)と、サーバー cron に切り替えたあと(右)の動きを並べたものです。もうひとつ、強いページキャッシュが効いているサイトでも似た現象が出やすい点も補足しておきます。アクセスがあってもキャッシュから返してしまって WordPress 本体までリクエストが届かないと、WP-Cron のチェックが走りません。raplsworks.com は WP Rocket を使っているので、なおさら呼ばれにくい構成だったのかもしれません。

WP-Cronの2つの動作モード比較図(左がデフォルトのアクセストリガー方式、右がサーバーcron方式)

第1部・失敗|Xserver の cron で叩いたら直り、数日後に WP Rocket が警告を出した

原因が見えれば、対策はシンプルです。アクセスが来たときにしか動かないのが問題なら、WordPress とは別のところから定期的に WP-Cron を呼べばいい。Xserver には共用レンタルでもサーバー cron の機能があるので、そこから一定間隔で wp-cron.php を叩きます。やることは、wp-config.phpDISABLE_WP_CRON を足してアクセス起動を止めることと、Xserver の Cron 設定で5分ごとに wp-cron.php を呼ぶこと。誰かが来たときに動く状態から、サーバー側で一定間隔ごとに動かす状態への切り替えです。間隔を5分にしたのは、テストで予定時刻からのズレを小さくしたかったから。1分は厳しすぎるし、15分だと夜中のテストで待たされる。最初から5分で組んで、負荷の体感に問題がなかったので、そのまま採用しました。どの程度の遅れなら許容できるかを先に決めておくのが、結局いちばん大事だったと思います。用途ごとの目安はこんな感じです。

WP-Cron安定化の全体像を示す2ステップの図解

実行間隔 向いている用途 考え方
5分ごと 予約メール、予約投稿、通知処理 遅れを小さくしたい場合
10分ごと 定期通知、軽いチェック処理 多少の遅れを許容できる場合
15分ごと バックアップ、集計、急がない処理 負荷をできるだけ抑えたい場合

まず wp-config.php を SSH か FTP で開いて、define('DISABLE_WP_CRON', true); の1行を足します。場所は「That’s all, stop editing!」のコメントより上です。これでアクセス起動は止まります。

注意:この1行を入れると、アクセス時に走るデフォルトの WP-Cron は止まります。サーバー cron をまだ設定していない状態でこれだけ入れると、予約投稿もメール送信も全部止まるので、必ず次のサーバー cron 設定までセットで終わらせてください。

wp-config.phpにDISABLE_WP_CRONを追加した編集画面

続けて、Xserver のサーバーパネルにログインして、Cron 設定のメニューを開きます。共用レンタルでもここから追加できるので、SSH は要りません。追加フォームには5分ごとの設定を入れます。分を */5 にして、時間・日・月・曜日はすべて *。各項目の意味は、下の図がわかりやすいです。

XserverサーバーパネルのCron設定メニューの場所

Cron 設定の各項目(分・時・日・月・曜日)の意味を一目で理解できる図解

コマンド欄には、最初こう入れていました。example.com を自分のドメインに置き換えます。末尾の > /dev/null 2>&1 は、実行結果や標準エラーを全部捨てるおまじないで、これを書かないと5分ごとに完了メールが届いて受信箱が大変になります。curl でも同じことができます。サイトを HTTPS で運用しているので、URL も https:// です。

XserverのCron設定画面にcurlコマンドと実行間隔を入力した状態

保存して、Cron 設定一覧に新しいジョブが並んでいれば、Xserver 側の登録は完了です。ここまでで、深夜のメールも5分以内に送信されるようになりました。最初の症状は、これでいったん解決しています。

XserverのCron設定一覧にジョブが追加された画面

解決した、と思って数日運用していたら、管理画面の WP Rocket のダッシュボードに、こんなエラーが出るようになりました。いくつかの予定タスクが実行に失敗していて、CRON が正しく動いていない可能性がある、失敗したのは Scheduled Cache Purge(キャッシュの自動クリア)と Scheduled Database Optimization(DB の自動最適化)だ、という内容です。

テストメール自体は、その間もちゃんと届いていました。機能としては動いている。でも WP Rocket から見ると、自分のスケジュールタスクが期限内に実行できていない、と判断されていた。一時的に詰まっただけかと思ったのですが、同じエラーが繰り返し出るので、これは構造的な問題だと考え直しました。

wget 構成で発生した問題のフロー図。サーバー cron が wp-cron.php を HTTP で叩いても、WP Rocket のページキャッシュ層が間に挟まり、リクエストが WordPress 本体まで届かない構造を示す概念図

HTTP リクエストで wp-cron.php を呼ぶ方式は、自分のサーバーが自分のサーバーの URL へ外向きの HTTPS を投げる動きです。raplsworks.com では、間に WP Rocket のページキャッシュが挟まっているので、cron が叩いたリクエストが、思った形で WordPress 本体まで届いていなかった可能性が高いと考えています。共用レンタルでは、そこへさらに WAF やプロキシといった自分が触れない中間層も加わるので、HTTP 経由の cron は意外と詰まりやすい構成です。この、自分が触れない中間層で思った通りに動かない感覚は、別件で WebSocket と SSE が使えず Long Polling に切り替えたときとも地続きでした(Xserver の WordPress 環境で WebSocket/SSE が使えない、代替実装)。HTTP 経由のままチューニングする道もありました。wp-cron.php をキャッシュ除外にする、専用の User-Agent を立てる、といった手です。ただ、メールは届いているのに WP Rocket は動いていないと言ってくる、という捻れを抱え続けるのは気持ちが悪い。そこで、wp-cron.php を HTTP で叩くこと自体をやめる方向に舵を切りました。

第2部・学び|WP-CLI で WP-Cron を直接呼ぶ構成に変えた

たどり着いたのは、Xserver の cron から WP-CLI 経由で WP-Cron のイベントを直接実行する構成です。WP-CLI は WordPress をコマンドラインから操作するツールで、Xserver の共用レンタルにも標準で wp コマンドが入っています。HTTP を介さず、サーバーローカルから WordPress を直接呼ぶので、外向きの HTTPS、WP Rocket のページキャッシュ、WAF、リダイレクトなど、間に挟まる要素が一切ありません。raplsworks.com の Cron 設定を、最終的にこの形にしました。

WP-CLI 構成のフロー図。サーバー cron が WP-CLI(wp コマンド)経由で WordPress 本体を直接呼び出す構造。HTTP リクエスト、WP Rocket キャッシュ、WAF、リダイレクトを一切経由しない

意味はシンプルです。/usr/bin/wp が Xserver の WP-CLI のフルパス、cron event run --due-now が実行予定時刻を過ぎた WP-Cron イベントをその場で全部実行する指定、--path= が WordPress のインストール先、--quiet が cron 向けに通常出力を抑える指定です。--path[XserverアカウントID] は契約ごとに違うので、サーバーパネルで確認するか、SSH で対象サイトに入って pwd を打つと正しいパスが見えます。

セキュリティ上の注意:サーバーパネルや SSH ログイン時に表示されるパス(/home/xs○○○○○○/...)には、自分の Xserver アカウント ID が含まれます。Cron コマンドのスクリーンショットを公開したり、フォーラム等に貼ったりするときは、ID 部分を必ずマスクしてください。私もこの記事ではマスク表記にしています。

この構成に切り替えてから、WP Rocket のダッシュボードに出ていた Scheduled Cache Purge / Scheduled Database Optimization の失敗エラーは出なくなりました。テストメールも引き続き5分以内に届きます。WP-CLI 経由はサーバー内部で完結しているので、トラブル時に見るレイヤーが減るのも楽でした。Xserver の共用レンタルで、しかも WP Rocket のようなキャッシュプラグインを入れた状態で WP-Cron を本格運用するなら、最初から WP-CLI 構成で始めるのも十分に合理的だと思います。

第2部・学び|動いていることを、複数の角度で確かめる

cron は、設定しただけだと動いているつもりになりやすいので、いくつかの角度から確認しています。いちばん分かりやすいのは、自分宛てのテストメールを数分後送信で予約してみることです。5分間隔の cron なので多少のズレは出ますが、5分前後で届けば WP-Cron が呼ばれている証拠になります。修正前は朝にまとめて届いていたメールが、修正後は予定時刻の5分以内に、夜中の予約でも安定して届くようになりました。

テストメールが時間通りに届いた受信箱のスクリーンショット

raplsworks.com でいちばん素直な指標になっているのは、WP Rocket のダッシュボードです。自分のスケジュールタスク(キャッシュの自動クリア、DB の自動最適化)が時間内に実行できていないと、上部に警告が出ます。wget 構成のときはここにエラーが出ていて、WP-CLI 構成に切り替えたあとは出なくなりました。WP Rocket を使っているサイトなら、cron 設定を変えたあと数日から1週間ほどダッシュボードを観察するのが、シンプルで確実です。ログも見ています。wget 構成のときはアクセスログに wp-cron.php へのアクセスが5分間隔で残っているかを確認していましたが、WP-CLI 構成ではアクセスログにリクエストは残りません。代わりに、Xserver のサーバーパネルで cron の実行履歴を見て、エラーになっていないこと、ジョブが想定通り動いていること、この2点が確認できれば安定して回っていると判断できます。あわせて、WP-Cron はメールだけでなく予約投稿や定期バックアップにも使われているので、メールの確認だけで終わらせず、他に WP-Cron に乗っている処理がないかも見ておきます。切り替えてから、深夜帯のメール送信が安定し、WP Rocket のエラーも止まり、朝にまとめて動く不自然な挙動も起きていません。

アクセスログにwp-cron.phpへの5分間隔の定期アクセスが記録されている画面

第2部・学び|二重送信を防ぐ、そして動かないときに見る場所

cron が安定したら、次に気になるのが二重送信です。何かの拍子に同じジョブが重なって走ったり、処理に時間がかかって次の起動と重なったりすると、同じ相手にメールが2通届くことがあります。Stripe の決済をきっかけにサンクスメールを送るプラグインを WordPress.org で公開したときも、同じ二重送信に直面しました。決済系のメールは特にここを厳しく作る必要があり、その設計思想は Thanks Mail for Stripe(WordPress.org) にも反映されています。raplsworks.com で動かしているのはその Custom 版で、WP-Cron 経由の予約メールでも考え方はほぼ同じでした。シンプルなやり方として、WordPress のトランジェントを使った短時間ロックがあります。

トランジェントを使ったロック機構のフロー図

これだけだと同じジョブが同時に動かないことしか防げないので、実運用ではもう一段の保険を入れています。たとえば、ユーザー meta や独自テーブルに、このジョブでは送信済み、と記録しておくと、万が一同じ処理が再実行されても、同じ相手に何度も送る事故は起きにくくなります。メールは一度送ると取り消しがききません。cron を安定させることに加えて、重複実行されても事故にならない設計をどこかに入れておくほうが、運用していて気が楽です。

サーバー cron まで設定したのに、まだ思ったように動かない場合は、いくつかの場所を順に見ます。まず、curlwgetwp のフルパスが合っているか。環境で違うので、which curlwhich wp で確認するのが早いです。次に、URL またはパスの指定。HTTP 構成ならドメイン、http と https の取り違え、www あり・なし。WP-CLI 構成なら --path= の指定先が正しいか。それから、ページキャッシュや Basic 認証で止まっていないか。これらがあると cron からの HTTP リクエストも詰まることがありますが、WP-CLI 構成なら内部から直接呼ぶので影響を受けません。WAF やセキュリティ設定で wp-cron.php が 403 などに弾かれていないかも、アクセスログで確認します。最後に、WP-Cron 側にイベントが登録されているか。サーバー cron が動いても、WordPress 側に予定イベントがなければ目的の処理は動かないので、wp cron event list で登録済みイベントを確認します。WAF の切り分けまで含めて面倒なら、最初から WP-CLI 構成にしてしまうのが手っ取り早いです。

よくある質問

DISABLE_WP_CRON だけ入れて、cron 設定を忘れたらどうなりますか?

予約投稿、定期バックアップ、プラグインの定期処理がすべて止まります。DISABLE_WP_CRON はアクセス時の WP-Cron 起動を止めるだけなので、サーバー cron をセットで設定しないと、時間を扱う処理が全部動かなくなります。必ず両方をセットで設定してください。

5分間隔だとサーバー負荷は気になりませんか?

raplsworks.com(月間 PV 1,500 程度)では、5分間隔で問題ありませんでした。ただ、これは個人ブログ規模の話です。アクセスが多いサイトや、WP-Cron で重い処理(大量メール送信、外部 API 連携、重いバックアップなど)を回しているサイトでは、まず10分や15分から始めて、必要に応じて短くするのが安全です。

サーバー cron と通常の WP-Cron を両方動かしてもいいですか?

動きはしますが、管理が面倒になるのでおすすめしません。実行回数が増え、処理が重なって二重実行のリスクも上がります。サーバー cron に寄せるなら、DISABLE_WP_CRON でアクセス起動を止めて一本化したほうが、トラブル時の切り分けも楽です。

予約投稿の遅延にも効きますか?

効きます。予約投稿も WP-Cron に乗っているので、アクセスの少ない時間帯に投稿時刻を設定していると、本来の時刻からずれて公開されることがあります。サーバー cron で WP-Cron を定期的に呼べば、予約時刻からのずれが小さくなります。

WP Rocket のエラー(Scheduled events failed to run)が消えないときは?

いま使っている cron が本当に wp-cron.php や WP-Cron のイベントを呼べているか、もう一度確認します。wgetcurl の場合、ページキャッシュが先に返って WordPress 本体まで届いていない、ということが起きがちです。WP Rocket 側で wp-cron.php をキャッシュ除外にするか、WP-CLI 経由に切り替えるかを検討してみてください。

wget と WP-CLI、最初に選ぶならどちらがいいですか?

動かすだけなら wgetcurl でも問題ありません。設定もシンプルで、最初の一歩としては入りやすいです。ただ、私の体験では wget 構成だと WP Rocket のダッシュボードにエラーが出るようになり、最終的に WP-CLI 構成に切り替えました。WP-CLI なら HTTP リクエストや WAF、ページキャッシュを経由せず、サーバー内部から WordPress を直接呼びます。切り分け対象が減るので、Xserver で本格的に運用するなら、最初から WP-CLI 構成にするほうが、結果的に楽だと思います。

まとめ|WP-Cron は「呼ばれて、はじめて動く」

WP-Cron を、時刻になったら勝手に走るもの、だと思い込んでいたのが、今回いちばんの落とし穴でした。実際は、誰かがアクセスしたタイミングで実行予定をチェックする仕組みなので、アクセスがない時間帯は止まったように見えます。raplsworks.com では、最終的にこの流れで安定させました。

作業 内容
1 wp-config.phpdefine('DISABLE_WP_CRON', true); を追加して、アクセス起動を止める
2 Xserver の Cron 設定で wp-cron.php を5分ごとに呼び出す(最初は wget 構成)
3 WP Rocket から「Scheduled events failed to run」が出たので、WP-CLI(wp cron event run --due-now)構成に切り替える
4 テストメール、WP Rocket のダッシュボード、cron の実行履歴で動作を確認する

予約メールが送られない、深夜だけ定期処理が動かない、と感じている方は、メール周りの設定を細かく見るより先に、そもそも処理が呼ばれているかを疑ってみてください。私もそこに気づくまで半日かかりました。症状から見える層と、本当に原因がある層が違う。WordPress のトラブルでは、これがよく起こります。そして、最初に動く構成にたどり着いたあとも、運用するなかで別の角度から問題と判断されることがあります。今回の wget から WP-CLI への切り替えが、まさにそうでした。動くものを少しずつ自分のサーバー環境にとって自然な形へ寄せていく。その地味な作業を、あなたはどこまで前倒しでやっておきますか。最初に動いた構成は、本当に、最後まで動き続ける構成でしょうか。

参考にした情報

  • WordPress Developer Resources – Plugin Handbook: Cron(確認日:2026年5月5日)
  • WordPress Developer Resources – Hooking WP-Cron Into the System Task Scheduler(確認日:2026年5月5日)
  • Xserver 公式マニュアル – Cron 設定(確認日:2026年5月5日)
  • WP-CLI 公式ドキュメント – wp cron event run(確認日:2026年5月5日)

関連記事

WordPress
この記事を書いた人
rapls

WordPressのプラグインを作っているフリーランスエンジニアです。Web開発はもう6年以上。WordPress.orgで Rapls AI Chatbot、Thanks Mail for Stripe、Rapls PDF Image Creator の3本を公開し、保守を続けながら、日本語ロケールの翻訳エディター(PTE)も務めています。このブログに書くのは、現場で自分が実際にハマって、調べて、直した話です。

raplsをフォローする
raplsをフォローする

コメント

タイトルとURLをコピーしました