DevKitLab Logo DevKitLab
Cron / crontab / スケジューリング / DevOps

Cron スケジュール早見表:毎分・毎時・毎日・毎週・毎月

欲しい間隔はもう分かっている——5 分ごと、平日 9 時、毎月 1 日——あとは一行だけ。よくある要件を、そのままコピーできる cron 式で並べます。直感で書くと外す数例には、正しい書き方も添えます。

多くの cron 解説は式から始めて、その読み方を教える。この記事は逆から入る。何をいつ実行したいかは、もう決まっている——5 分ごと、平日の 9 時、毎月 1 日——あとは貼り付ける一行だけだ。以下、よくある要件を、そのままコピーできる式として間隔ごとにまとめる。

コピーするときに覚えておきたいことが二つある。大半の例は標準の五フィールド crontab(分・時・日・月・曜日)で、プラットフォーム固有の書き方にはその旨を添えている。構文の「なぜ」を知りたければ、「Cron 式の読み方」を読むとよい。さらに、「35 分ごと」「30 秒ごと」「第一月曜」「毎月末」のように、直感どおりに書くと外しやすい要件には、式だけでなく正しい方法も添える。

N 分ごと

* * * * *      # 毎分
*/5 * * * *    # 5 分ごと
*/15 * * * *   # 15 分ごと
*/30 * * * *   # 30 分ごと

分フィールドの */N は「N 分ごと」で、0 から数える。*/15 は :00、:15、:30、:45 に動く。思いどおりになるのは、N が 60 を割り切るときだけだ——2、3、4、5、6、10、12、15、20、30。

*/35 は 35 分ごとではない。ステップはフィールドの先頭から数え、毎時リセットされる。だから */35 は :00 と :35 に動き、時が変わるとまた :00 から始まる——35 分の間隔のあとに 25 分の間隔、これが延々と続く。60 を割り切れない間隔はすべてこうなる(*/45 は 45 分のあと 15 分)。本当に 35 分や 90 分の間隔が要るなら、cron の分フィールドでは表せない。固定間隔の systemd timer か別のスケジューラを使う。

N 時間ごと

0 * * * *      # 毎時、正時に
0 */2 * * *    # 2 時間ごと(00:00、02:00、04:00……)
0 */6 * * *    # 6 時間ごと(00:00、06:00、12:00、18:00)
15 * * * *     # 毎時の 15 分

分フィールドに 0 を置けば、ジョブは毎時一回、正時にだけ動く——分を * のままにして時をステップするのは、よくある間違いだ。「割り切る」規則は 24 時間の範囲にも同じく効く。*/2*/3*/4*/6*/8*/12 は等間隔。*/5 は 0、5、10、15、20 時に動き、次は翌日の 0 時なので、等間隔ではない。

毎日、決まった時刻に

0 0 * * *      # 毎日 深夜 0 時(00:00)
30 6 * * *     # 毎日 06:30
0 9 * * *      # 毎日 09:00
0 22 * * *     # 毎日 22:00(午後 10 時)

分が先、時が後、どちらも 24 時間表記だ。30 6 は 06:30。逆にして 6 30 と書けばエラーになる。30 時は存在しないからだ。フィールドの並び順そのものが意味を決める。

一日に何度か

0 9,17 * * *     # 一日二回、09:00 と 17:00
0 0,12 * * *     # 一日二回、00:00 と 12:00
0 8-18 * * *     # 正時に、08:00 から 18:00
0 9-17/2 * * *   # 09:00 から 17:00 まで 2 時間ごと(9、11、13、15、17)

カンマは特定の値を並べ、ステップつきの範囲(9-17/2)は範囲内をその刻みで進む。範囲の終端も含まれる8-18 は 18:00 を含み、9-17/2 は 17:00 を含む。

日付をまたぐ時間帯

0 22-23,0-6 * * *   # 毎時、22:00 から翌 06:00 まで

範囲は小さい値から大きい値へしか指定できないため、22-6 は夜間の時間帯として使えない——cron は深夜 0 時で範囲を折り返さず、パーサによってはエラーになるか、何にもマッチしない。深夜 0 時の前後で二つに分ける。22-230-6 だ。曜日でも同じで、「金曜から月曜」は 5-1 ではなく 5,6,0,1 と書く。

特定の曜日

0 9 * * 1-5       # 平日(月〜金)09:00
0 9 * * 1         # 毎週月曜 09:00
0 9 * * 0         # 毎週日曜 09:00(0 = 日曜)
0 9 * * 6,0       # 週末(土 + 日)09:00
0 9 * * MON-FRI   # 平日、名前で書く

:曜日の番号には移植性がない。標準 cron は 0–7 で数え、0 と 7 はどちらも日曜だ。だが Cloudflare Workers は 1=日曜 から 7=土曜 まで数えるので、そこでの 1-5 は日曜〜木曜であって、月曜〜金曜ではない。Kubernetes は 0 を日曜として扱う。プラットフォームが名前を受け付けるなら、MON-FRISUN のように名前で書けば番号の違いを避けられる。(方言の差は読み方ガイドにある。)

平日の営業時間内、N 分ごと

*/15 9-17 * * 1-5   # 15 分ごと、09:00 から 17:xx、月〜金
0 9-17 * * 1-5      # 正時に、09:00 から 17:00、月〜金

よくある複合スケジュールで、左から読むと「15 分ごと、9 時から 17 時のあいだ、平日」となる。注意点が一つある。時の範囲の終端も含まれるため、9-17 は 17 時台全体を含む——ジョブは 17:15、17:30、17:45 と動き続け、18:00 になって初めて止まる。17:00 きっかりで終えたいなら、ステップの範囲を 9-16 に狭め、17 時の正時用に一行足す。0 17 * * 1-5 だ。

毎月、そして特定の日

0 0 1 * *      # 毎月 1 日の深夜 0 時
0 0 15 * *     # 毎月 15 日
0 9 1 1,7 *    # 1 月 1 日と 7 月 1 日 09:00(年二回)
0 0 1 */3 *    # 四半期の初日(1、4、7、10 月)

ここには罠が三つある。第一に、「毎月末」を表す素直なフィールドはない——日は固定の数字で、月の長さはまちまちだ。L で表せる(0 0 L * *)が、L方言拡張(Quartz など少数)で標準 cron ではない。使う前にプラットフォームの対応を確かめる。移植できる代替は次節にある。

第二に、日と曜日の「または」規則。日フィールドと曜日フィールドの両方を限定すると、標準 cron はどちらかが一致したときに動く。両方そろったときではない。だから 0 0 13 * FRI は「毎月 13 日、加えて毎週金曜」であって、「13 日の金曜日」ではない。本当に「または」を意図しない限り、日と曜日のどちらかは * のままにする。

第三に、その月にない日は、その月は動かないだけだ。0 0 31 * * は 31 日ある七つの月にしか動かず——2、4、6、9、11 月を飛ばす——0 0 30 2 *(2 月 30 日)は一度も動かない。月の長さによらず「月末」が欲しいなら、30 や 31 を決め打ちせず、次節のガードを使う。

毎月第 N 曜日

「第一月曜」「最終金曜」——月次レポートやメンテナンス枠でよく使われ、同時に「または」規則が問題になる場面でもある。つい書きたくなる 0 9 1-7 * 1間違いだ。日(1-7)と曜日(1)の両方を限定するので、cron はそれを「または」で組み合わせ、1 日から 7 日の毎日加えて毎週月曜に動く。移植性を保つには、日付の範囲をスケジュールし、曜日の判定はジョブの中で行う。

# 毎月第一月曜 09:00 —— 1〜7 日には必ず月曜が一つだけある
0 9 1-7 * *  [ "$(date +\%u)" = 1 ] && /srv/app/monthly.sh

date +%u は ISO の曜日(1 = 月曜……7 = 日曜)。このガードで、ジョブは 1〜7 日のうちの月曜だけ先へ進む。%\% にエスケープされている点に注意——crontab のコマンド内でエスケープしていない % は、コマンドを途中で切ってしまう(この罠は「なぜ cron ジョブが動かないのか」で扱う)。「最終金曜」なら、毎週金曜に動かし、次の金曜が別の月かどうかをスクリプトに判定させる。Quartz なら専用構文で表せる。MON#1 が第一月曜、6L が最終金曜だ。

秒単位:「30 秒ごと」

標準 cron の最小の粒度は一分だ。秒フィールドはないので、「30 秒ごと」は五フィールドでは書けない。cron だけで試す回避策は二つある。信頼性が必要なら、より適した選択肢もある。

# 1. 六フィールドの cron(Spring、多くのライブラリ)は秒を先頭に置く:
*/30 * * * * *              # 30 秒ごと、六フィールドが使える場合

# 2. 五フィールドを二行、片方を sleep でずらす:
* * * * *              /path/to/job
* * * * *  sleep 30 && /path/to/job

六フィールド形式は、秒フィールドを受け付けるスケジューラでしか動かない——フィールドを数えて、六つなら先頭が秒だ——古典的な Unix crontab は五フィールドで、これを拒否する。sleep 30 の対は一分単位の crontab で使える実用的な回避策だが、正確な間隔にはならない。二つの起動は独立しているため、一回の処理が 30 秒を超えると重なることもある。本当に秒単位で信頼性が要るなら、OnUnitActiveSec=30ssystemd timer か、自前のループを持つ常駐プロセスのほうが cron より向く。

名前つきの短縮形

Cronie / Vixie 系の crontab は、五フィールド全体の代わりに、いくつかの名前つき短縮形を受け付ける。

@hourly    # 0 * * * *
@daily     # 0 0 * * *   (@midnight と同じ)
@weekly    # 0 0 * * 0
@monthly   # 0 0 1 * *
@yearly    # 0 0 1 1 *   (@annually と同じ)
@reboot    # デーモンの起動時に一度

読みやすいが、一部の crontab しか対応しない拡張だ——GitHub Actions や Cloudflare のようなホスティングサービスのスケジューラは五フィールド形式を求める。とくに @reboot は起動するマシンが必要なので、サーバーレスのプラットフォームでは動かない。迷ったら、短縮形が表す明示的な五フィールドを使う。

cron のフィールドで言えないとき

五フィールドでは書けない日付条件がある。毎月末、第一月曜、祝日を除く毎平日などだ。方法は同じ——必要より頻繁にスケジュールし、実際に動くかどうかはジョブ自身に決めさせる。cron が表せない条件をラッパースクリプトの先頭に置き、条件に合わなければすぐ終了する。

0 0 * * *  /srv/app/eom.sh      # 毎日動かして……
#!/bin/bash
# eom.sh —— 毎月末だけ先へ進む
[ "$(date -d tomorrow +%d)" = "01" ] || exit 0
# …… 本当の処理 ……

同じ形で残りも扱える。毎週金曜に動かし、次の金曜が別の月でなければ終了する(最終金曜)。毎平日動かし、祝日リストの日付なら終了する。90 分ごとのような固定間隔は、毎時実行のガードに無理に押し込まず、固定間隔で動くスケジューラを使う。cron で適切な時間帯まで絞り込み、最後の判断はスクリプトに任せる。(date -d は GNU coreutils。BSD や macOS では date -v+1d +%d を使う。)

出す前に確かめる

上で示した五フィールドの式は標準 cron だが、この記事では L、秒フィールド、名前つき短縮形のようなプラットフォーム固有の拡張にも触れている。同じ五フィールドの式でも、曜日の番号や実行時のタイムゾーンによって意味が変わることがある。ほとんどの間違いは、二つの習慣で捕まえられる。

  • ツールで読み返す。式を Cron 式ツールに貼る。スケジュールを平易な言葉で示し、選んだタイムゾーンで次の実行時刻を並べるので、「平日に 15 分ごと」は思い込みではなく確認になる。(@daily のような短縮形は先に五フィールドへ展開する——ツールが解析するのは数値フィールドで、マクロではない。)
  • タイムゾーンに気を配る。cron 行は自分のタイムゾーンを持たない。0 9 * * * は「9:00」だが、それはランタイムが使うゾーンでの話だ——サーバーのローカル時刻か、多くのホスティングサービスでは UTC になる。ここが重要なら、ツールは選んだゾーンで各実行を見せてくれる。サーバーと自分の時差を突き合わせるには、タイムゾーン変換ツールを使う。「UTC、GMT、ISO 8601、Unix 時間の違い」は、その理由をほどいてくれる。

正しいと確かめた行がそれでも動かないなら、次は環境、crontab の置き場所、ホスト自体を確認する。「なぜ cron ジョブが動かないのか」で詳しく扱っている。

早見表

やりたいことその一行
毎分* * * * *
5 分ごと*/5 * * * *
15 分ごと*/15 * * * *
毎時、正時に0 * * * *
2 時間ごと0 */2 * * *
毎日 深夜 0 時0 0 * * *
毎日 09:000 9 * * *
一日二回(09:00、17:00)0 9,17 * * *
夜間、毎時(22:00–06:00)0 22-23,0-6 * * *
平日 09:000 9 * * 1-5
毎週月曜 09:000 9 * * 1
週末 09:000 9 * * 6,0
営業時間、15 分ごと*/15 9-17 * * 1-5
毎月 1 日 深夜 0 時0 0 1 * *
四半期の初日0 0 1 */3 *
毎月第一月曜 09:000 9 1-7 * * + 曜日ガード
毎月末毎日動かす + date ガード