公開日 · 読了時間 2 分
ゼロから学ぶcron式: ビジュアルガイド
5フィールドのPOSIX cron文法、特殊文字、ステップ値、Quartzの拡張、AWSのcronとの違い、そして今日crontabにコピペできる十数個の実例を学びましょう。
なぜcronは今もどこにでもあるのか
cronは1979年、Version 7 UnixのためにブライアンカーニハンによってBrian Kernighanによって書かれました。最新のLinuxシステムが今も同梱している版、1987年にPaul Vixieが書いたVixie cronは、環境変数、ユーザーごとのcrontab、そして親切な@rebootショートカットを追加しましたが、元の5フィールドのスケジュール形式はほぼ手つかずのままです。その安定性こそが、World Wide Web以前に設計された文法が、2026年になってもKubernetesマニフェスト、GitHub Actionsワークフロー、AWS EventBridgeのルール、SpringのScheduledアノテーションに貼り付けられている理由です。
Stack Overflowから行をコピペするだけでcronを学ぶと、スケジュールは遅かれ早かれ予想外の動きをします。毎日のバックアップが毎分走ったり、週末のジョブが火曜日に発火したり、夏時間の境界でタイムゾーンがずれたり。本ガイドではゼロから文法をたどり、その後で十分な実例を示し、任意の式の形を一目で見抜けるようにします。
5フィールド形式
POSIX crontabの行は、スケジュールに続けてコマンドを書きます。スケジュールはちょうど5つの空白区切りフィールドです: 分、時、日(月内)、月、曜日。各フィールドは数値、リスト、範囲、ステップ、またはワイルドカード*を受け付けます。
* * * * * command-to-run
┬ ┬ ┬ ┬ ┬
│ │ │ │ │
│ │ │ │ └─── 曜日 (0-6, 日曜 = 0; ほとんどの実装では7も日曜)
│ │ │ └───── 月 (1-12 または JAN-DEC)
│ │ └─────── 日(月内) (1-31)
│ └───────── 時 (0-23)
└─────────── 分 (0-59)特殊文字: * , - / とその仲間
古典的なcronでは5つの文字でほぼすべての仕事をします。それぞれの意味を覚えれば、ほとんどの式は文章のように読めます。
- * — このフィールドのすべての値。分フィールドの*は「毎分」を意味します。
- , — 離散値のリスト。分フィールドの0,15,30,45は1時間に固定された4分を意味します。
- - — 両端を含む範囲。曜日フィールドの1-5は月~金を意味します。
- / — ステップ値。range/stepまたは*/stepと書きます。分の*/10は「0から始まる10分ごと」を意味します。
- L, W, # — Quartzの拡張で、POSIXの一部ではありません。L = 最後、W = 最も近い平日、# = 月のN番目の曜日。詳細は後述。
0,30 * * * * 毎時 :00 と :30
*/5 * * * * 5分ごと (0, 5, 10, 15 ...)
0 9-17 * * 1-5 月-金、09:00から17:00まで毎時
0 0 1,15 * * 毎月1日と15日の深夜
15 14 * * 0 毎週日曜14:15ステップ値は思っているものとは違う
ステップ演算子/は範囲が暗黙の場合でも、常に範囲に対して働きます。分フィールドの*/15は0-59/15の省略形で、0、15、30、45で発火します。だから分の*/7は「今から7分ごと」ではなく「0~59の中の7の倍数」を意味し、0、7、14、21、28、35、42、49、56となり、深夜への切替前に4分の隙間ができます。本当のローリング間隔が必要なら、ジョブランナーやOnUnitActiveSec付きのsystemdタイマーを使ってください。
ステップは範囲の途中から始めることもできます。分の5-59/10は5、15、25、35、45、55で発火します。長いカンマリストを書かずに非対称なスケジュールを作りたいときに、範囲とステップを組み合わせてください。
*/15 * * * * 00, 15, 30, 45 (良い)
*/7 * * * * 00, 07, 14, 21, 28, 35, 42, 49, 56, その後4分の隙間 (たぶん意図と違う)
5-59/10 * * * * 05, 15, 25, 35, 45, 55
0 */4 * * * 00:00, 04:00, 08:00, 12:00, 16:00, 20:00日(月内)と曜日: ORルール
cron最大の落とし穴: 日(月内)と曜日の両方が制限されている(どちらも*でない)場合、Vixie cronは論理ANDではなく論理ORとして扱います。0 0 13 * 5は毎月13日の深夜AND毎週金曜日に発火し、13日の金曜日だけではありません。AND動作を得たい場合は、2つのフィールドのうち1つだけを制限してもう1つはスクリプト側でフィルターするか、「特定値なし」を表す?プレースホルダーをサポートするQuartzのようなスケジューラを使ってください。
0 0 13 * 5 毎月13日の深夜 OR 毎週金曜 (Vixie cronのORルール)
0 0 * * 5 毎週金曜の深夜
0 0 13 * * 毎月13日の深夜
# Quartz: 0 0 0 13 * 5 ? なら13日の金曜日に特定的にマッチコピペ可能な10個の例
この表を印刷してモニターの横に貼ってください。あなたが書く本番のcron行のほとんどは、これらのわずかなバリエーションです。
* * * * * 毎分
0 * * * * 毎時 :00
*/15 * * * * 15分ごと
0 0 * * * 毎日深夜 (サーバーローカル時間)
0 9 * * 1-5 平日09:00
30 14 * * 1-5 平日14:30
0 9 * * 1 毎週月曜09:00
0 22 * * 0 毎週日曜22:00
0 0 1 * * 毎月1日の深夜
0 0 1 1 * 1月1日の深夜
0 3 * * 6 毎週土曜03:00 (典型的なバックアップ窓)
*/5 9-17 * * 1-5 平日の業務時間中、5分ごと名前付きショートカット
Vixie cron、GNU mcron、そしてほとんどの派生はいくつかの@プレフィックス付きエイリアスを受け付け、よくあるスケジュールに対応づけます。等価な5フィールドより読みやすく、誤字も起こりにくいです。
@yearly 0 0 1 1 * と同じ (@annuallyも同じ)
@monthly 0 0 1 * * と同じ
@weekly 0 0 * * 0 と同じ
@daily 0 0 * * * と同じ (@midnightも同じ)
@hourly 0 * * * * と同じ
@reboot システム起動時に1回実行Quartz: 6・7フィールドのいとこ
Javaのスケジューラ — Quartz本体、Springの@Scheduled(cron = ...)、いくつかのクラウドプラットフォーム — は別の方言を使います。Quartzは秒フィールドを先頭に追加し、必要に応じて年フィールドを末尾に追加し、合計6または7フィールドになります。L(最後)、W(最も近い平日)、#(月のN番目の曜日)、そして「特定値なし、日と曜日のどちらか一方だけが指定されている」を意味する?プレースホルダーもサポートします。
Quartzの式をLinuxのcrontabにコピーすると静かに誤解釈されます。先頭の0は通常の分の値に見えるからです。ファイルを保存する前に、スケジューラがどの方言を期待しているか必ず確認してください。
Quartzのフィールド: 秒 分 時 日 月 曜日 [年]
0 0 12 * * ? 毎日正午
0 15 10 ? * MON-FRI 平日10:15
0 0 0 L * ? 毎月最終日の深夜
0 0 0 LW * ? 毎月最終平日
0 0 9 ? * 2#1 毎月第1月曜の09:00
0 0 9 ? * 6#3 毎月第3金曜の09:00
0 0 0 1 * ? 2026 毎月1日の深夜、ただし2026年のみAWSのrate vs cron式
EventBridge、CloudWatch Events、LambdaはQuartzに近いcron文法を使いますが、2つの重要な差があります。秒フィールドはなく(7ではなく6フィールド)、日(月内)か曜日のどちらかに?を必ず使う必要があります(両方を*にはできません)。AWSは単純な間隔向けにrate(value unit)も提供しており、ORの落とし穴を完全に回避できます。
AWS cronのフィールド: 分 時 日(月内) 月 曜日 年
cron(0 12 * * ? *) 毎日12:00 UTC
cron(0/15 * * * ? *) 15分ごと
cron(0 9 ? * MON-FRI *) 平日09:00 UTC
cron(0 0 1 * ? *) 毎月1日のUTC深夜
rate(5 minutes) 5分ごと
rate(1 hour) 毎時
rate(7 days) ルール作成から7日ごとタイムゾーン、夏時間、02:30に壊れるもの
Vixie cronはシステムのタイムゾーン(TZ環境変数か/etc/localtimeが指すもの)に従います。夏時間で1時間が飛ばされると、その隙間に予定されたジョブは静かにスキップされます。標準時に戻るときは、重複した時間内のジョブが実装によっては2回走るか、1回だけ走るかが変わります。安全なデフォルトは2つ: cronをUTCで動かすか、01:00~04:00の窓を避けてスケジュールすることです。
クラウドのスケジューラは挙動が異なります。EventBridgeは常にUTC。Kubernetes CronJobsはv1.27で追加されたspec.timeZoneフィールドを尊重します。QuartzはJVMのデフォルトを使い、CronTriggerにタイムゾーンを渡さない限りそのままです。設定するスケジューラごとに、必ずドキュメントを一度読んでください。前のものの慣習がそのまま通用すると仮定しないこと。
発火しない式のデバッグ
10回中9回は式自体は問題なく、環境が間違っています。スケジュールを責める前にこのチェックリストをたどってください。
- cronデーモンのログを読む: Debian/Ubuntuではgrep CRON /var/log/syslog、systemdホストではjournalctl -u cron。ログにはcronが実際に実行した解決済みコマンドラインが表示されます。
- ユーザーを確認する。フラグなしのcrontab -lはあなた自身のcrontabを表示します。気になるスケジュールは/etc/crontab、/etc/cron.d/*、または別ユーザーのcrontabにあるかもしれません。
- PATHを確認する。cronは最小限のPATH(通常/usr/bin:/bin)で動きます。絶対パスを使うか、crontabの先頭でPATH=を設定してください。
- シェルを確認する。cronはログインシェルではなく/bin/shを使います。bash固有の機能(プロセス置換、[[ など)は明示的にbash -cでラップする必要があります。
- 出力をリダイレクトする。> /var/log/myjob.log 2>&1 なしでは、失敗したジョブはローカルのrootにメールが届き、あなたは見ません。
- タイムゾーンを確認する。ジョブ内でdate && date -uすると両方を記録できます。スケジュールが想定したものと一致しなければ、そこがバグです。
# より安全なcron行:
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=""
*/5 * * * * /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1cronが向かない場面
cronは固定された時刻のスケジュールに優れ、単一ホスト上の1行ジョブには無敵です。一方で次の場合には適しません: 多数のマシン間での分散調整(キュー、リーダー選出付きスケジューラ、concurrencyPolicy: Forbid付きKubernetes CronJobsを使う)、失敗時のリトライとバックオフ(スクリプトをラップするかワークフローエンジンを使う)、分以下の粒度(systemdタイマーやイベント駆動トリガーを使う)、前回の実行が終わるのを待つ必要があるスケジュール(cronは前回がまだ走っていても平気で2つ目を起動します)。早めに限界を見極めると、深夜3時のページャーが大幅に減ります。
ビジュアルに作って検証する
1か月に2行以上cronを書くなら、各式をコミット前にパーサーで照合する習慣が最もコスパが高い投資です。Multilitiesは無料の/tools/cron-builderを提供し、5または6フィールドの式を平易な日本語にデコードし、次の10回の発火時刻をローカルタイムゾーンで表示します。これは日(月内)/曜日のORの罠と、*/7型ステップのサプライズを捕まえる、まさにそのクロスチェックです。既知のアンカー日付に対する次回発火時刻をアサートする小さな単体テストと組み合わせれば、スケジュールジョブは退屈なほど信頼できるものになります — それこそが望ましい姿です。