
cronバッチの失敗に気づく仕組み|静かに止まるジョブを見逃さない
月初の朝、経理から「先月の集計データが、まだ更新されていないみたいです」と連絡が来る。 調べてみると、毎晩動いているはずの集計バッチが、3日前から失敗したまま止まっていた。エラー自体は出ていた。でも、その出力は誰も見ていないメールボックスにたまっていただけだった——。
そんな経験、ありませんか。 定期バッチや夜間ジョブは、動いているうちは何も言ってきません。だからこそ、失敗しても画面には何も出ず、「静かに止まったまま」になりやすい。保守運用の現場で、いちばん見つけにくいトラブルのひとつです。
この記事では、なぜcronが失敗を教えてくれないのか、そして「失敗したら通知」「動いたことを確認」「ログを残す」の3つで気づける仕組みを、一人運用の目線で整理します。全部を一度に入れなくて大丈夫です。まずは、いちばん止まると困る1本から。
結論:バッチの見逃しは、①失敗したら通知(終了コードを見て、失敗時だけ知らせる)②動いたことを確認(成功の合図が来なかったら鳴らす=外側からの死活監視)③ログを残す(いつ・成否・所要時間)——の3つで防げます。まずは「いちばん止まると困る1本の出力を、届くメールに飛ばす」ところから。
コマンドはLinuxのcronを前提にしています。閾値や通知先は用途次第です。ここを出発点に、環境に合わせて調整してください。
なぜcronは失敗を教えてくれないのか
cron(指定した時刻に自動でコマンドを実行してくれる仕組み)は、とても素直な道具です。「決めた時間に、決めたコマンドを動かす」——それだけをします。うまくいったか、失敗したかを、こちらに知らせに来てくれるわけではありません。
見逃しが起きるのは、だいたい次のどれかです。
- 出力の行き先が「見ていない場所」:cronは標準出力・標準エラーを、既定ではそのサーバー上のローカルメール(
MAILTOの宛先。未設定ならジョブの所有者)に送るだけです。誰もログインして読まなければ、エラーはたまり続けるだけになります。 - 終了コードを見ていない:処理の途中で失敗しても、スクリプト全体が最後まで流れて
exit 0(成功)で終われば、外からは「正常に終わった」ように見えます。失敗を失敗として扱えていない状態です。 - そもそも動かなかった:cron自体が止まっている、
crontabの記述ミス、前提のファイルが無くて即終了——このケースはエラーログすら残らないことがあります。「何も起きていない」がいちばん怖いパターンです。 - 多重起動でおかしくなる:前回の処理が終わる前に次が動き出し、データが二重になったり、ロック待ちで詰まったりする。失敗ではないのに結果が壊れる、という見えにくい形です。
ポイントは、「エラーが出るか」ではなく「エラーに気づけるか」が問題だということ。仕組みがなければ、静かに止まったバッチには誰も気づけません。
気づくための3本柱

見逃しのパターンが分かれば、対策もシンプルになります。次の3つを、できるところから一段ずつ。
① 失敗したら通知する(終了コードを見る)
まず、失敗したときだけ知らせる仕組みを入れます。毎回通知が来ると読まなくなるので、「失敗時だけ」が大事です。
ラッパースクリプトで包むのがいちばん確実です。処理を実行し、終了コードが 0 以外なら通知する、という形です。
#!/bin/bash
set -o pipefail
LOG=/var/log/myapp/nightly.log
名前="nightly-batch"
# 本体を実行し、出力はログへ
/path/to/nightly_job.sh >>"$LOG" 2>&1
code=$?
if [ "$code" -ne 0 ]; then
# 失敗時だけ通知(メール・Slack Webhookなど、届く経路に)
tail -n 30 "$LOG" | mail -s "[FAILED] $名前 (exit=$code)" you@example.com
fi
exit "$code"
crontab 側では、宛先を「実際に読むアドレス」にしておきます。
MAILTO="you@example.com"
0 2 * * * /path/to/run_nightly.sh
set -o pipefail を付けると、パイプの途中で失敗したときにちゃんと失敗扱いになります。スクリプト内で複数コマンドを順に実行するなら、先頭に set -e を置いて「どこかで失敗したら止める」のも有効です(意図的に失敗を許す箇所だけ || true で逃がす)。
② 動いたことを確認する(成功の合図が来なかったら鳴らす)
①だけでは、「①のスクリプトごと動かなかった」「cronが止まっていた」ケースを拾えません。失敗の通知は、動いて初めて送られるからです。
そこで、成功したら“生きてます”の合図を送り、その合図が予定時刻を過ぎても来なかったら鳴らす、という外側からの監視を足します。いわゆるハートビート(死活監視)や、来なければ鳴る「デッドマンスイッチ」の考え方です。
- 成功時に、監視サービスの専用URLへアクセス(ping)する。例:処理の最後に
curl -fsS --retry 3 https://監視サービス/ping/xxxxを実行。 - 監視サービス側は「毎晩2時台に合図が来るはず」と知っていて、来なかったら通知してくれる。
外形監視サービス(Healthchecks系など)を使うと最小の手間で入りますが、自前でも「最終成功時刻をファイルやDBに記録し、別のジョブがそれを見て“何時間も更新がなければ鳴らす”」で代用できます。大事なのは、判定を処理の外側に置くこと。処理が丸ごと止まっても、外側は「来なかった」に気づけます。
③ ログを残す(いつ・成否・所要時間)
通知で気づけても、原因にたどり着けなければ復旧に時間がかかります。最低限、いつ動いて・成否は・どれくらいかかったかを1行でも残しておきます。
start=$(date +%s)
echo "[$(date '+%F %T')] start $名前" >>"$LOG"
# …本体…
end=$(date +%s)
echo "[$(date '+%F %T')] end $名前 exit=$code elapsed=$((end-start))s" >>"$LOG"
所要時間を残しておくと、「失敗はしていないけど、最近やけに遅い」という予兆にも気づけます。ログが増えるので、ためこまない設定(ログローテーション)とセットにしておくと安心です。
具体例:多重起動を防ぐひと工夫(flock)
「失敗ではないのに結果が壊れる」原因で多いのが、前回の処理が終わらないうちに次が動く多重起動です。flock を使うと、実行中は次の起動をスキップ(またはエラー)にできます。
# 実行中なら二重起動しない(-n = 待たずに即やめる)
0 2 * * * /usr/bin/flock -n /tmp/nightly.lock /path/to/run_nightly.sh
-n を付けると、前の処理がまだ動いていた場合はすぐ終了します(終了コードは非0になるので、①の通知にも乗ります)。「詰まって二重に動く」を、1行で静かに防げます。
なお、cronにこだわらなくてもよい環境なら、systemd のタイマー(systemd.timer)に載せ替えると、実行結果が systemctl status や journalctl で追いやすくなり、失敗時の扱いも整理しやすくなります。今の仕組みを大きく変えられないうちは、まず①〜③をcronのまま足すので十分です。
影響:仕組みを整えると、何が変わるか
- 失敗したその朝のうちに気づけて、被害が広がる前に手を打てる
- 「動かなかった」パターンも外側の監視で拾えて、沈黙の見逃しが減る
- ログに成否と所要時間が残り、原因調査と“遅くなってきた”予兆に強くなる
- 手順を残せば、自分が休みの日でも誰かが同じように確認・対応できる
放置すると、バッチの失敗は「数日後に、業務側からの指摘で発覚する」形になりがちです。そうなると、止まっていた期間のデータ補正まで含めて対応が重くなります。静かに進むぶん、気づく仕組みさえあれば、ずいぶん軽くできます。
明日やること:いちばん止まると困る1本から
- バッチの一覧を出す(
crontab -l、/etc/cron.d/、/etc/cron.daily/などを確認)。まず「止まると業務が止まる1本」を選ぶ。 - その1本の
MAILTOを、実際に自分が読むアドレスに変える。まずはこれだけで「失敗が届く」状態になる。 - 余力があれば、その1本を①のラッパーで包み、失敗時だけ通知するようにする(成功時は静かに)。
- さらに余力があれば、②の「成功の合図が来なかったら鳴らす」を1本だけ入れる。
- ③として、開始・終了・成否・所要時間をログに1行ずつ残す。
この30分で、「いちばん止まると困るバッチ」だけは、静かに死なない状態になります。
バッチの見逃しを防ぐチェックリスト
最低ライン(優先順位つき:これだけで回る) 1) 届く宛先:重要バッチの MAILTO が「実際に読むアドレス」になっている 2) 失敗を拾う:終了コードを見て、失敗時だけ通知するラッパーで包む 3) 沈黙を拾う:成功の合図が来なかったら鳴らす監視を、重要な1本に入れる
余力が出たら拡張
- 全バッチへ①〜③を横展開/
flockで多重起動を防止 - 所要時間の記録から「遅くなってきた」予兆を拾う
systemdタイマーへ載せ替えてjournalctlで追えるようにする
免除条件(省略可)
- 使い捨て検証用や、失敗しても業務に影響しない一時的なジョブは②を省略可。ただし「失敗時通知」と「ログ1行」は残す。
確認項目
- 動いているバッチの一覧(cron/
cron.d/cron.daily/タイマー)を把握している - 重要バッチの
MAILTOが、実際に読むアドレスになっている - 終了コードを見て、失敗時だけ通知するようになっている
-
set -o pipefail(必要ならset -e)で、途中失敗を失敗として扱えている - 「成功の合図が来なかったら鳴らす」外側からの死活監視が、重要な1本に入っている
- 開始・終了・成否・所要時間がログに残り、ためこまない設定になっている
-
flockなどで多重起動を防いでいる(結果が壊れるのを防止) - 失敗時にまず何を見るか(対象ログ・再実行手順)が1枚にまとまっている
よければ、こちらも
バッチの死活監視は、障害を「言われてから」ではなく「起きたその朝に」拾うための備えのひとつです。鳴った後の段取りや、原因にたどり着くログの見方も、あわせて型にしておくと落ち着けます。
- 監視アラートの鳴らしすぎを減らす|閾値の決め方と運用のコツ
- 500エラーが出たら最初に見る5つのログ|場所と確認の順番
- 障害対応runbookテンプレート|初動・切り分け・連絡・記録を1枚に
- ディスク使用率100%を防ぐ|ログローテーションと監視の基本

バッチの失敗は、画面に何も出ないぶん、気づいたときには時間がたっていることが多いトラブルです。でも、静かに進むからこそ、気づく仕組みを一度だけ作っておけば、あとはずっと守ってくれます。 今日は、いちばん止まると困る1本の宛先を、自分に届くアドレスに変えるだけで十分です。その小さなひと手間が、「数日後に指摘されて青ざめる朝」を、静かに遠ざけてくれます。
ほかの実務ヒントは記事一覧からどうぞ。保守運用の小さな備えを、メールでも少しずつお届けしています。