インフラ・運用 2026.07.24

本番サーバーで「誰も気づかなかった」障害——cronとキューの死活監視を最初に設定すべき理由

約7分で読めます

cronジョブやキューワーカーが静かに停止していても、エラーログには何も残らない。本番環境で見落としがちな「無音障害」の実態と、Laravelで今すぐ実装できる死活監視の具体的な手順を解説します。

こんな状況、心当たりはありませんか?

「毎日夜中に自動でメールが送られているはずなのに、お客様から『最近メールが届かない』と言われた」——そんな報告が、ある日突然飛んでくる。

あるいは、バックグラウンドで注文データを処理するキューが止まっていて、気づいたのは3日後。その間に数百件のジョブが積み上がっていた……という事態は、決して珍しくありません。

cronジョブやキューワーカーが止まったとき、サーバーはエラーを吐かないのです。HTTPリクエストと違い、プロセスが死んでいても画面には何も表示されない。アクセスログにも残らない。監視ツールが反応しない。気づく手段がなければ、障害は静かに続きます。

この記事では、実際のクライアント案件で起きた「無音障害」の事例をもとに、LaravelのcronとキューワーカーをRailのように死活監視する実践的な実装手順を解説します。


なぜ「誰も気づかない」のか——無音障害の構造

Webアプリケーションの一般的な障害は、HTTPステータスコードやエラーログとしてすぐに可視化されます。しかしcronとキューは、本質的に非同期・バックグラウンドで動作するという特性上、失敗しても能動的に誰かへ通知しません。

典型的な「無音障害」のパターンは3つあります。

  • cronジョブが実行されていない:サーバーのcrontab設定が消えていた、PHP-CLIのパスが変わっていた、schedule:runコマンド自体がクラッシュしていた
  • キューワーカーがメモリ不足で落ちた:長時間稼働によるメモリリークでプロセスが終了し、Supervisorも再起動を諦めていた
  • ジョブが失敗し続けている:最大リトライ回数を超えたジョブがfailed_jobsテーブルに積み上がっているが、誰も確認していない

こうした問題が厄介なのは、ビジネスへの影響が時間差で表れる点です。メール通知が止まれば顧客離れにつながり、受注処理が詰まれば手動対応のコストが発生する。「何か変だな」と気づいた頃には、すでに数日分のダメージが積み上がっています。


サーバー・インフラでお困りですか?

障害対応・移行・パフォーマンスチューニングなど、ご相談ください

無料で相談する

実際の案件で起きたこと

あるECサイトのクライアントから、「定期会員へのリマインドメールが届いていない」という連絡を受けたのは、障害発生から5日後のことでした。

調査すると、原因はサーバーリプレイス時にcrontabの設定を移行し忘れていたこと。php artisan schedule:runを1分ごとに呼び出すエントリが丸ごと消えていたため、メール送信スケジュールが完全に止まっていました。

ログには何も残っていません。500エラーも出ていない。外形監視ツール(死活監視URL)のアラートも鳴っていない。なぜなら、Webサーバーは正常に動いていたからです。

この経験を機に、当社ではすべてのLaravelプロジェクトにHeartbeat(ハートビート)監視とキュー監視を標準セットとして組み込むようにしました。


具体的な実装手順

1. Heartbeat監視でcronの「生存確認」を自動化する

Heartbeat監視とは、「定期的に外部サービスへ『生きています』と信号を送り、一定時間信号が途絶えたらアラートを上げる」という仕組みです。

代表的な無料サービスとして Healthchecks.io があります。アカウントを作成し、チェックのURLを発行したら、Laravelのスケジューラに組み込みます。

// app/Console/Kernel.php

protected function schedule(Schedule $schedule): void
{
    // 例: 毎日深夜に会員リマインドメールを送信
    $schedule->command('mail:send-reminders')
             ->dailyAt('02:00');

    // Heartbeat: 毎分 healthchecks.io へ ping を送る
    $schedule->call(function () {
        $pingUrl = config('services.healthchecks.ping_url');
        if ($pingUrl) {
            Http::timeout(5)->get($pingUrl);
        }
    })->everyMinute()->name('healthcheck-ping')->withoutOverlapping();
}

.env に以下を追加します。

HEALTHCHECKS_PING_URL=https://hc-ping.com/your-unique-token
// config/services.php

'healthchecks' => [
    'ping_url' => env('HEALTHCHECKS_PING_URL'),
],

これで、cronが止まると10分以内(期間は設定可能)にメール・Slackへ通知が届きます。

2. キューワーカーの監視——Supervisorとアラートの組み合わせ

キューワーカーの運用には Supervisor を使うのが定石ですが、Supervisorが再起動を試みて諦めた後はプロセスが落ちたままになります。これをLaravel側でも検知する仕組みを追加します。

; /etc/supervisor/conf.d/laravel-worker.conf

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work redis \
    --sleep=3 \
    --tries=3 \
    --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/worker.log
stopwaitsecs=3600

この監視コマンドをスケジューラに登録します。

// Kernel.php に追加
$schedule->command('queue:monitor-health')->everyFiveMinutes();

3. failed_jobs テーブルの定期レポートを仕組み化する

失敗したジョブは failed_jobs テーブルに保存されますが、放置しがちです。週次で自動レポートをSlackに送る仕組みを作っておくと、問題の予兆を早期に掴めます。

$schedule->command('queue:failed-report')->weeklyOn(1, '09:00');
// queue:failed-report コマンドの核心部分
$failures = DB::table('failed_jobs')
    ->selectRaw('connection, queue, COUNT(*) as count')
    ->where('failed_at', '>=', now()->subWeek())
    ->groupBy('connection', 'queue')
    ->get();

// Slack通知などに渡す

よくある失敗パターンと対処法

失敗1:「Supervisorが動いているから大丈夫」という過信

Supervisorはプロセス管理ツールですが、ジョブの中身の正常性は保証しません。ワーカーが起動していても、全ジョブがタイムアウトで失敗し続けているケースがあります。ジョブの実行結果レベルでの監視は別途必要です。

失敗2:--tries を無制限にする

リトライ回数を設定しないと、壊れたジョブが永遠にキューに残り、ワーカーリソースを食い続けます。--tries=3--backoff=60(秒)を必ずセットしましょう。また、ジョブクラス側でも $tries$backoff を明示することを推奨します。

失敗3:開発環境でしか動作確認していない

QUEUE_CONNECTION=sync(同期実行)のまま本番にデプロイすると、非同期処理がそもそも意図どおりに動きません。本番環境では redis または database ドライバを使い、staging環境でもワーカーを起動した状態でテストすることが必須です。

失敗4:サーバー移行時にcrontabを確認しない

冒頭の事例がまさにこれです。crontab -l の内容を .crontab.bak としてリポジトリに含めておくか、インフラをコード化(Ansible等)することで防げます。

flowchart TD
    A[本番サーバー稼働中] --> B{cronは動いているか?}
    B -->|はい| C{キューワーカーは生存中?}
    B -->|いいえ| X[❌ 無音障害 — 誰も気づかない]
    C -->|はい| D{failed_jobsは増えていないか?}
    C -->|いいえ| X
    D -->|正常| E[✅ Healthchecks.io にping送信]
    D -->|失敗急増| F[⚠ Slack/メールにアラート]
    E --> G[監視サービスがタイムアウトを検知]
    G -->|pingあり| H[問題なし]
    G -->|pingなし| I[🚨 アラート送信]

サーバー・インフラでお困りですか?

障害対応・移行・パフォーマンスチューニングなど、ご相談ください

無料で相談する

サーバー管理、丸ごとお任せください

サーバー保守・運用

監視・障害対応・パフォーマンス改善まで、安定稼働をサポートします

200件以上の制作実績 顧客満足度97% 初回相談無料

※ 通常1営業日以内にご返信します

まとめと次のステップ

cronとキューの死活監視は、「あったほうがいい機能」ではなく、本番稼働の前提条件です。HTTPレイヤーの監視だけでは、バックグラウンド処理の障害は永遠に見つかりません。

Healthchecks.ioのような外部Heartbeatサービスは無料プランでも十分機能し、実装コストも1〜2時間程度です。それだけで、「5日後に気づく障害」を「10分で検知できる問題」に変えられます。

当社Fivenine Designでは、Laravelプロジェクトの新規開発・既存サイトの改修いずれにおいても、こうした死活監視の仕組みを標準で組み込んでいます。「今動いているサイトに監視が入っているか確認したい」「本番環境のインフラ設定を見直したい」という場合は、お気軽にご相談ください。

この記事をシェア

サーバー・インフラの課題を解決します

構築・移行・監視・障害対応まで、安定した運用環境をご提供します。 初回相談は無料です。

※ 1営業日以内にご返信いたします

この技術でお困りなら

無料でプロに相談できます

相談する
AIに無料相談