インフラ・運用 2026.09.07

cronが止まっていても誰も気づかない——中小企業に必要な最低限の監視設計

約15分で読めます

「バッチが3日前から止まっていた」「メール通知が届いていなかった」——本番環境のcron監視は後回しにされがちです。最低限の仕組みで障害を即座に検知する方法を解説します。

こんな経験、ありませんか?

「昨日の夜中にバッチ処理が止まっていたらしく、朝になってお客さんから問い合わせが来てはじめて気づいた」

これは他人事ではありません。実際に弊社がサポートに入った案件でも、3日間cronが止まっていたのに誰も気づいていなかったというケースがありました。注文確認メールの送信ジョブが止まっていたため、ユーザーへの通知が途絶え、クレームが殺到した——というリアルな話です。

中小企業のWeb担当者が陥りがちなのが「サーバーは動いているからcronも動いているはず」という思い込みです。Webサイト自体が表示されていると、バックグラウンドの処理まで正常稼働していると錯覚してしまう。しかし現実には、cronは驚くほど静かに、誰にも気づかれず止まります。

この記事では、専任のインフラエンジニアがいない中小企業でも導入できる「最低限の監視設計」を、具体的な実装例とともに解説します。


なぜcronは「黙って」止まるのか

cronが止まる主な原因

まず、cronが止まりやすい背景を理解しておく必要があります。cronはそもそも失敗を通知する仕組みを標準では持っていません。コマンドが0以外の終了コードを返しても、デフォルトでは何も起こりません。

止まる原因として頻出するのは以下のパターンです。

  • PHPのメモリ不足:データが増えてバッチ処理がメモリ上限に達する
  • DB接続タイムアウト:長時間バッチでDBの接続が切れる
  • ファイルパスの変更:デプロイ後にartisanコマンドのパスがずれる
  • パーミッションエラー:サーバー移行後に実行権限が失われる
  • 依存サービスの障害:外部APIが落ちてジョブがエラー終了する

LaravelのSchedulerを使っている場合も油断は禁物です。php artisan schedule:run をcronで毎分呼び出す構成が一般的ですが、そのcron自体が止まれば当然Laravelのスケジューラも動きません。


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

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

無料で相談する

最低限の監視設計:3つの柱

監視設計は「検知」「通知」「記録」の3つを揃えることで機能します。どれか一つでも欠けると、問題が起きても対応が遅れます。

flowchart LR
    A[cronジョブ実行] --> B{正常終了?}
    B -->|Yes| C[Healthcheck pingを送信]
    B -->|No| D[エラーログ記録]
    C --> E[監視サービスが受信確認]
    D --> F[Slackへアラート通知]
    E --> G{一定時間pingなし?}
    G -->|Yes| F
    G -->|No| H[正常監視継続]

柱1:Dead Man's Snitch(死活監視)

「定期的に生存報告を送り、報告が途絶えたら異常とみなす」という発想が、cron監視の核心です。これをDead Man's Snitchパターンと呼びます。

OSSなら Healthchecks.io、商用なら Dead Man's Snitch や Cronitor が有名です。いずれも無料プランが使えます。

LaravelのSchedulerに組み込む場合、withoutOverlapping() と組み合わせてこのように書けます。

// app/Console/Kernel.php

protected function schedule(Schedule $schedule): void
{
    $schedule->command('orders:send-confirmation')
        ->everyFiveMinutes()
        ->withoutOverlapping()
        ->thenPingUrl('https://hc-ping.com/your-unique-uuid') // 成功時にping
        ->onFailure(function () {
            // 失敗時はSlack通知(後述)
            \Log::error('orders:send-confirmation failed');
        });
}

thenPingUrl() はジョブ正常終了後に指定URLへGETリクエストを送る便利メソッドです。Healthchecks.io側で「5分以内にpingが来なければSlackに通知する」と設定しておけば、cronが止まった瞬間にアラートが飛びます。

柱2:ジョブの実行ログをDBまたはファイルに残す

pingだけでは「動いた」「止まった」の2値しかわかりません。「何に失敗したか」を調査できるようにするため、ジョブの実行結果は必ずログに残してください。

LaravelのJobクラスを使っている場合、failed() メソッドに記録処理を入れるのがシンプルです。

// app/Jobs/SendOrderConfirmation.php

public function handle(): void
{
    // 処理本体
    \Log::channel('cron')->info('SendOrderConfirmation: started', [
        'order_id' => $this->orderId,
        'timestamp' => now()->toIso8601String(),
    ]);

    // ... 処理 ...

    \Log::channel('cron')->info('SendOrderConfirmation: completed', [
        'order_id' => $this->orderId,
    ]);
}

public function failed(\Throwable $exception): void
{
    \Log::channel('cron')->error('SendOrderConfirmation: failed', [
        'order_id' => $this->orderId,
        'error'    => $exception->getMessage(),
        'trace'    => $exception->getTraceAsString(),
    ]);
}

config/logging.phpcron チャンネルを追加して、ログファイルを専用に分けておくと後の調査がしやすくなります。

// config/logging.php の channels に追加

'cron' => [
    'driver' => 'daily',
    'path'   => storage_path('logs/cron.log'),
    'level'  => 'debug',
    'days'   => 30,
],

柱3:Slack通知で即時アラート

ログを残すだけでは能動的に異常に気づけません。エラー発生時にSlackへ通知する仕組みを入れることで、担当者がリアルタイムで検知できます。

LaravelであればNotificationクラスを使うのが最もスマートです。

// app/Notifications/CronJobFailed.php

use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\SlackMessage;

class CronJobFailed extends Notification
{
    public function __construct(
        private string $jobName,
        private string $errorMessage
    ) {}

    public function via(object $notifiable): array
    {
        return ['slack'];
    }

    public function toSlack(object $notifiable): SlackMessage
    {
        return (new SlackMessage)
            ->error()
            ->content(":rotating_light: *本番サーバーのcronジョブが失敗しました*")
            ->attachment(function ($attachment) {
                $attachment
                    ->title('ジョブ名: ' . $this->jobName)
                    ->fields([
                        'エラー内容' => $this->errorMessage,
                        '発生日時'   => now()->format('Y-m-d H:i:s'),
                        '環境'       => config('app.env'),
                    ]);
            });
    }
}

よくある失敗パターン4選

サーバーのメール送信設定が壊れているとき(よくある)、cronのエラーメールも届きません。メール監視に全振りするのは二重のSPOF(単一障害点)を作ることになります。Slackやwebhookベースの通知を優先してください。
本番環境のcron設定が正しいか、デプロイのたびに確認する習慣がないチームは多いです。特にCapistranoやGitHub Actionsで自動デプロイを入れた直後に「パスがずれた」「Symlink先が変わった」などのトラブルが多発します。デプロイ後に `php artisan schedule:list` で確認するステップをCI/CDに組み込みましょう。
前のジョブが終わっていないのに次のジョブが走り、DBのレコードが二重更新されるという事故は珍しくありません。必ず `withoutOverlapping()` を入れ、さらにキャッシュロックのTTLを処理時間の最大想定値に合わせて設定してください。
特に人の入れ替わりがあったチームで発生します。監視ツールの通知先は個人アカウントではなく、チャンネル(例: `#alerts-production`)に設定するのが鉄則です。担当者が変わっても通知先を変える必要がなくなります。

実案件での導入事例

あるECサイトのクライアントでは、毎朝6時に在庫同期バッチが動く設計でした。ところがサーバー移行のタイミングで、cronファイルのPHPパスが古いまま残ってしまい、4日間バッチが沈黙していたことが発覚。在庫データがズレたまま販売が続き、欠品商品の注文が発生してしまいました。

弊社で監視設計を入れ直した結果、Healthchecks.io + Slack通知の組み合わせにより、次回のパス問題は15分以内に検知できるようになりました。担当者に「何かが止まった」という感覚が生まれ、それだけで運用の安心感が大きく変わった——とお客様からフィードバックをいただいています。


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

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

無料で相談する

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

サーバー保守・運用

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

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

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

まとめと次のステップ

「監視は大企業のやること」という認識は、もう時代遅れです。無料ツールと数十行のコードで、中小企業でも十分な死活監視が実現できます。重要なのは「完璧な監視」ではなく、「止まったことに気づける最低限の仕組み」 を今すぐ動かすことです。

既存のLaravelプロジェクトへの導入であれば、Healthchecks.io + thenPingUrl() だけなら半日で入れられます。まずそこから始めてみてください。

「既存のプロジェクトにどう入れればいいかわからない」「サーバーの構成を見直したい」という場合は、Fivenine Designにご相談ください。現状のcron設計をヒアリングした上で、御社の規模に合った監視設計をご提案します。


この記事をシェア

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

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

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

この技術でお困りなら

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

相談する
AIに無料相談