「バッチが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.php に cron チャンネルを追加して、ログファイルを専用に分けておくと後の調査がしやすくなります。
// 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選
実案件での導入事例
あるECサイトのクライアントでは、毎朝6時に在庫同期バッチが動く設計でした。ところがサーバー移行のタイミングで、cronファイルのPHPパスが古いまま残ってしまい、4日間バッチが沈黙していたことが発覚。在庫データがズレたまま販売が続き、欠品商品の注文が発生してしまいました。
弊社で監視設計を入れ直した結果、Healthchecks.io + Slack通知の組み合わせにより、次回のパス問題は15分以内に検知できるようになりました。担当者に「何かが止まった」という感覚が生まれ、それだけで運用の安心感が大きく変わった——とお客様からフィードバックをいただいています。
サーバー管理、丸ごとお任せください
サーバー保守・運用
監視・障害対応・パフォーマンス改善まで、安定稼働をサポートします
※ 通常1営業日以内にご返信します
まとめと次のステップ
「監視は大企業のやること」という認識は、もう時代遅れです。無料ツールと数十行のコードで、中小企業でも十分な死活監視が実現できます。重要なのは「完璧な監視」ではなく、「止まったことに気づける最低限の仕組み」 を今すぐ動かすことです。
既存のLaravelプロジェクトへの導入であれば、Healthchecks.io + thenPingUrl() だけなら半日で入れられます。まずそこから始めてみてください。
「既存のプロジェクトにどう入れればいいかわからない」「サーバーの構成を見直したい」という場合は、Fivenine Designにご相談ください。現状のcron設計をヒアリングした上で、御社の規模に合った監視設計をご提案します。