cron 式の書き方完全入門 — 毎月末・毎週金曜・うるう年対応

カレンダーとスケジュール表 Photo: Unsplash

「毎月末の夜 23:55 にバックアップを回したい」「平日の朝 9 時 15 分だけ集計ジョブを動かしたい」——やりたいことはシンプルなのに、cron 式を書き始めた瞬間に手が止まる。あの * * * * * の並びを見ても、どこが分でどこが曜日かすぐ思い出せないし、*/3 が「3 の倍数の分」なのか「3 分おき」なのかで毎回迷う。本記事では 5 フィールド cron の基本から、Vixie cron・Quartz・AWS EventBridge・GitHub Actions の方言差、日と曜日を両方指定したときの落とし穴、そして「毎月最終日」をどう表現するかまで、実例を中心に総ざらいします。読み終わるころには crontab -e の真っ白な画面を前に固まらなくなるはずです。

cron の「cron」は Chronos から

cron が初めて世に出たのは 1975 年、Bell Labs で産声をあげた Unix Version 7 に付属する小さなユーティリティでした。名前の由来はギリシャ神話の時の神 Chronos(クロノス)。時間を司る神の名を冠したこの道具は、以降 UNIX 系 OS にとって「定期実行の代名詞」として根付いていきます。現在 Linux 系ディストリビューションに広く採用されているのは、1987 年に Paul Vixie が実装した Vixie cron の系譜で、環境変数の扱いや @hourly のような特殊文字列を導入し、以降の Debian・RHEL 系で事実上の標準となりました。40 年以上現役を続けている、ソフトウェアとしてはかなり長寿な部類に入ります。

基本フォーマット(5 フィールド)

まずは基本形を押さえましょう。標準的な cron 式は空白区切りの 5 つのフィールドで構成されています。左から順に「分・時・日・月・曜日」。右側のフィールドほど粒度が粗くなる、と覚えるのが早いです。

┌──────── 分      (0 - 59)
│ ┌────── 時      (0 - 23)
│ │ ┌──── 日      (1 - 31)
│ │ │ ┌── 月      (1 - 12)  または JAN-DEC
│ │ │ │ ┌ 曜日    (0 - 6)   または SUN-SAT   (0 = 日曜)
│ │ │ │ │
* * * * *  実行したいコマンド

取り得る値の範囲を表にまとめました。うっかり「13 月」「32 日」と書いてもパーサはエラーになりますし、環境によっては 無言で無視されるので、値域は頭に入れておくと事故が減ります。

位置フィールド値域別名
1分 (minute)0 〜 59
2時 (hour)0 〜 23
3日 (day of month)1 〜 31
4月 (month)1 〜 12JANDEC
5曜日 (day of week)0 〜 6SUNSAT

一部の実装(AWS EventBridge、Quartz Scheduler、Spring の @Scheduled)では、先頭に「秒」フィールドが追加されて 6 フィールドになったり、末尾に「年」フィールドが追加されたりします。同じ cron 式でも 5 と 6 では意味がまったく変わるので、ドキュメントでどちらの形式を要求しているかを必ず確認してください。「AWS にそのまま Linux の cron 式を貼り付けてしまって動かない」は日常茶飯事の事故です。

特殊文字(* / , - ?

cron 式で実際に使うのは数字そのものよりも、数字を組み合わせる 特殊文字です。5 つだけ覚えれば、9 割のスケジュールは書けます。

文字意味読み方
*そのフィールドの全ての値* * * * *毎分
,列挙(カンマ区切りの値)0 9,12,18 * * *毎日 9 時・12 時・18 時
-範囲(開始-終了)0 9-17 * * *毎日 9 時〜17 時の毎正時
/ステップ(刻み幅)*/15 * * * *15 分おき
?「どれでも」(日/曜日のみ)0 9 ? * MON曜日を指定するので日は無視(Quartz 方言)

* は「任意の値」ではなく「ありうる全ての値」を意味する点に注意してください。「毎分」ではなく「全ての分で」という気持ちで読むと、0 * * * *(毎時 0 分)のようにほかのフィールドで絞り込んだときの挙動が腑に落ちます。

/ はステップ値で、*/5 は「0, 5, 10, 15, ...」という 5 の倍数の集合を表します。「5 分おき」と完全に一致するので感覚的には問題ないのですが、*/7 のように 60 の約数でない数字を書くと「0, 7, 14, 21, 28, 35, 42, 49, 56」で次は 60 に到達せず「0」に戻るため、58 分や 59 分には実行されません。「7 分おきに実行」と言いたくて書くと、時をまたぐ瞬間だけ間隔がズレる、という地味な罠があります。

? は Vixie cron には存在せず、Quartz 系(AWS EventBridge、Spring、Jenkins の一部プラグイン)で使われる方言です。「日と曜日は両方同時には指定できない」という Quartz 独特のルールがあり、片方を指定したらもう片方は必ず ? にする、という約束になっています。Linux の crontab で ? を書くと文法エラーになるので要注意。

これ以外に、Vixie cron では頻出パターンを短縮した 特殊文字列も使えます。読みやすさ重視で、毎日定時のような典型スケジュールならこちらのほうが分かりやすいです。

特殊文字列等価な cron 式意味
@yearly / @annually0 0 1 1 *毎年 1 月 1 日 0 時
@monthly0 0 1 * *毎月 1 日 0 時
@weekly0 0 * * 0毎週日曜 0 時
@daily / @midnight0 0 * * *毎日 0 時
@hourly0 * * * *毎時 0 分
@reboot(対応なし)システム起動時に 1 回

@reboot は Vixie cron 固有の拡張で、Cron デーモンが起動したタイミングで 1 回だけ実行される、一種の起動スクリプト代わりです。ただし systemd が主流になった現代では、同じ目的なら systemd unit を使うほうが適切な場面が多いです。

基本例(毎分・毎時・毎日)

手になじませるために、基本中の基本から順番に見ていきます。まずは「何もフィルタしない」パターン。

# 毎分(1 分ごと)
* * * * *

# 毎時 0 分(毎時間ちょうど)
0 * * * *

# 毎日 0 時 0 分(毎日深夜)
0 0 * * *

# 毎日 0 時(@daily と等価)
@daily

# 5 分おき
*/5 * * * *

ここで改めて確認してほしいのが 0 * * * *(毎時 0 分)と * 0 * * *(0 時台の毎分)の違いです。両者は見た目が似ていますが、前者は「1 時間に 1 回」、後者は「0 時 00 分〜0 時 59 分まで合計 60 回」。左から「分、時」の順という感覚が崩れると、後者のような「0 時台の 1 時間だけ全力で回る」ジョブを誤って書いてしまうので、指差し確認するくらいのつもりでいいです。

「5 分おき」のように等間隔で動かしたい場合は */5 の形が便利ですが、前述のとおりステップ値が 60 の約数でないと最後だけズレます。60 の約数は「1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60」なので、このどれかを選ぶと綺麗です。7, 8, 9, 11, 13 などを使いたくなったら、一度立ち止まったほうが安全です。

よく使う例 20

現場で頻出する 20 パターンを一気に並べます。そのままコピペで使えます。上のフィールド順は「分 時 日 月 曜日」です。

# 1. 毎分
* * * * *

# 2. 毎時 0 分(毎時間ちょうど)
0 * * * *

# 3. 5 分おき
*/5 * * * *

# 4. 15 分おき
*/15 * * * *

# 5. 30 分おき(= 毎時 0 分と 30 分)
0,30 * * * *

# 6. 毎日 0 時 0 分
0 0 * * *

# 7. 毎日 朝 9 時 0 分
0 9 * * *

# 8. 毎日 朝 9 時と夜 18 時
0 9,18 * * *

# 9. 平日の朝 9 時(月〜金)
0 9 * * 1-5

# 10. 週末の朝 10 時(土日)
0 10 * * 0,6

# 11. 毎週月曜 朝 8 時
0 8 * * 1

# 12. 毎週金曜 夜 18 時
0 18 * * 5

# 13. 毎月 1 日 0 時
0 0 1 * *

# 14. 毎月 1 日 3 時 30 分
30 3 1 * *

# 15. 毎月 15 日 正午
0 12 15 * *

# 16. 毎月 1 日と 15 日の朝 9 時
0 9 1,15 * *

# 17. 6 時間おき(0, 6, 12, 18 時)
0 */6 * * *

# 18. 平日の 8 時〜18 時、15 分おき
*/15 8-18 * * 1-5

# 19. 毎年 1 月 1 日 0 時(年次バッチ)
0 0 1 1 *

# 20. 毎日 深夜 2 時 30 分(ピーク帯を避けたバックアップ)
30 2 * * *

特に 9 番「平日の朝 9 時」は業務ジョブで一番よく出るパターンです。「月〜金だけ」を 1-5 と書くのが cron のセオリー。日曜始まり(Sun=0)の文化なので、月曜は 1、金曜は 5 になります。曜日を文字列で MON-FRI と書ける実装も多く、可読性を取るならそちらもあり。

18 番の */15 8-18 * * 1-5 は「平日の営業時間だけ 15 分おきに動かす」タイプで、オフィス内向けの監視ジョブなどに重宝します。注意点として、8-18 は「8 時台から 18 時台まで」なので、18 時 45 分にも実行されます。18 時 00 分で切りたいなら */15 8-17 * * 1-5 とし、別途 0 18 * * 1-5 を書くか、シンプルに 8-17 にしてしまうのが楽です。

毎月末日を表現する方法

cron でもっとも難しいテーマのひとつが「毎月末日」です。月ごとに 28/29/30/31 日と日数が違うため、「日」のフィールドにマジックナンバーを書くだけでは表現できません。Vixie cron は仕様としてこの概念を持たず、単純には書けないので回避策が必要です。

一方で Quartz 系(AWS EventBridge、Spring の @Scheduled、Jenkins 一部プラグイン)では L(Last の略)という特殊文字が用意されており、「日」フィールドに L を書くだけで「その月の最終日」を意味します。

# Quartz 系(6 フィールド: 秒 分 時 日 月 曜日)
# 毎月末日の 23 時 55 分
0 55 23 L * ?

# AWS EventBridge(6 フィールド: 分 時 日 月 曜日 年)
# 毎月末日の 23 時 55 分
cron(55 23 L * ? *)

では Vixie cron で同じことをやりたいときはどうするか。定番のパターンは「毎日 0 時に実行するようにして、スクリプト側で翌日が 1 日かどうかを判定する」方法です。

# crontab: 毎日 23:55 に判定スクリプトを実行
55 23 * * * /usr/local/bin/run-if-last-day.sh
#!/bin/bash
# run-if-last-day.sh
# 翌日が 1 日なら今日が月末と判定
TOMORROW=$(date -d tomorrow +%d)
if [ "$TOMORROW" = "01" ]; then
  /path/to/monthly-backup.sh
fi

この方式はシェルスクリプトの date コマンドに依存しますが、GNU date と BSD date で引数が異なるので注意してください。macOS なら date -v +1d +%d、GNU(Linux)なら date -d tomorrow +%d です。もうひとつの書き方として、date +%d で今日の日付を取り、28/29/30/31 日のどれかに当たっているかを比較する手もあります。こちらのほうが date の方言差に引っかかりにくく、複数 OS で運用するなら無難です。

「28-31 日を全部指定」は罠

0 23 28-31 * * と書いて「28〜31 日のどこかが末日」と解決した気になると、2 月以外の月では 4 日連続で実行されてしまいます。当たり前ですが、cron は「日付 28 から 31 までのうち存在する日」をすべて拾うので、1 月 28 日・29 日・30 日・31 日と 4 回走ります。必ず翌日判定ロジックと組み合わせてください。

曜日の数値(0 vs 7 の方言差)

cron の曜日フィールドは 0 = 日曜、1 = 月曜、…、6 = 土曜が POSIX 標準です。ところがここに罠があります。歴史的経緯で 7 も日曜として扱う実装が多く、Vixie cron も 07 の両方を日曜として受け付けます。

数値曜日(標準)文字列(大文字小文字区別なし)
0日曜日SUN
1月曜日MON
2火曜日TUE
3水曜日WED
4木曜日THU
5金曜日FRI
6土曜日SAT
7(Vixie のみ)日曜日

「じゃあ 7 でも書けるなら好きにしていいじゃん」と思うかもしれませんが、POSIX 厳格モードや一部の BSD cron、Kubernetes CronJob のバリデータでは 7 を弾くものがあります。可搬性を優先するなら 日曜は 0 と書くのが無難です。筆者は機械的に「0 から 6 の閉区間」というルールを自分に課して、7 は書かないようにしています。

さらに古い SysV cron では曜日の文字列エイリアス(SUNSAT)を受け付けない実装もあります。リモート環境で動かすスクリプトを書くときは、「数値で書く」「0 を使う」の 2 点を守れば事故りません。

タイムゾーン問題

「JST で毎日 9 時に動かしているはずが、実はサーバーの UTC で 9 時に動いていた」——これは cron の失敗事例トップクラスの頻度で発生します。cron デーモンは原則として システムのタイムゾーンに従って動くため、サーバーのタイムゾーン設定がずれていると指定した時刻とは 9 時間ずれて走ります。

まずは運用中のサーバーが何のタイムゾーンで動いているかを確認する癖をつけましょう。Linux なら次のコマンドで一発です。

# システムのタイムゾーンを確認
timedatectl
# または
date
# または
cat /etc/timezone

サーバー全体を変えるなら sudo timedatectl set-timezone Asia/Tokyo が定番ですが、世界各地のリージョンに配置した複数台を個別設定すると事故の原因になります。特定のジョブだけタイムゾーンを指定したいときは、crontab ファイルの先頭で環境変数を設定する方法が使えます(Vixie cron 拡張)。

# crontab の先頭で TZ を設定
TZ=Asia/Tokyo
MAILTO=alert@example.com

# この cron 式は JST 9 時に実行される
0 9 * * * /path/to/morning-batch.sh

Kubernetes の CronJob では spec.timeZone フィールドが v1.25 から GA になり、timeZone: "Asia/Tokyo" と書けるようになりました。それ以前のクラスタでは UTC のみ、という地獄を経験した人も多いはずです。GitHub Actions の on.schedule.cronUTC 固定で、タイムゾーン指定はできません。JST で「朝 9 時」を表現したいなら UTC 換算で 0 0 * * * と書く必要があります。

夏時間(DST)の罠

タイムゾーンが DST(サマータイム)を使う地域だと、年 2 回「同じ時刻が 2 回ある日」と「同じ時刻が存在しない日」が発生します。アメリカ東海岸の America/New_York で「毎日 2:30」に設定すると、春の切り替え日は 2:30 が存在せずスキップ、秋の切り替え日は 2:30 が 2 回出現して二重実行、という事故が起きます。DST を使わない Asia/Tokyo(JST)は幸いこの問題に無縁ですが、グローバル展開するサービスでは サーバーも cron も UTC で動かすのがいちばん安全です。

方言比較

cron と一口に言っても、実装ごとに微妙な差があります。同じ式でも動く環境と動かない環境があるので、主要な 4 系統をまとめておきます。

項目Vixie cron (Linux)Quartz / AWS EventBridgeGitHub Actionssystemd timer
フィールド数56 (秒 or 年付き)5独自構文 (OnCalendar)
L (最終日)非対応対応非対応非対応(代替あり)
W (最寄り平日)非対応対応非対応非対応
# (第 N 曜日)非対応対応非対応非対応
? (どれでも)非対応必須非対応
曜日の 7日曜として受付実装依存受付
タイムゾーン指定TZ= 環境変数管理画面で設定UTC 固定Unit で指定
@hourly対応一部のみ非対応

AWS EventBridge は 6 フィールド型で、末尾に「年」フィールドがあります。「日」と「曜日」を同時指定できず、片方は必ず ? を入れるという Quartz ルールを踏襲しています。Quartz の # は「第 N 曜日」を表す便利な文字で、5#3 は「その月の第 3 金曜日」の意味。Vixie cron にはない機能なので、給与計算のように「第 2 水曜日に走らせたい」要件があるならこちらを使う価値があります。

# AWS EventBridge: 平日 9 時(JST 運用なら別途 TZ 調整)
cron(0 9 ? * MON-FRI *)

# AWS EventBridge: 毎月最終金曜の 17 時
cron(0 17 ? * 6L *)

# AWS EventBridge: 第 2 水曜の 14 時
cron(0 14 ? * 4#2 *)

GitHub Actions の YAML ワークフローで定期実行するなら、on.schedule に cron 式を書きます。注意点は 2 つで、タイムゾーンは UTC 固定、そして 混雑するタイミング(毎時 0 分)は実行が遅延または欠落しやすいこと。GitHub 公式も「0 分ちょうどに設定しているワークフローは集中するので、ピークを避けた時刻をおすすめ」と明記しています。

# .github/workflows/nightly.yml
name: Nightly Build
on:
  schedule:
    # UTC 0 時(JST 9 時)毎日
    - cron: "0 0 * * *"
    # 毎週月曜 UTC 3 時(JST 12 時)
    - cron: "0 3 * * 1"
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: ./build.sh

systemd timer は cron とは全く別物で、OnCalendar=*-*-* 09:00:00 のような独自構文を使います。cron 式を直接書くことはできないので、移行するときは書き直しが必要です。systemd-analyze calendar "Mon *-*-* 09:00:00" で次の実行時刻をドライランで確認できるのが強みです。

よくある間違い

cron の失敗パターンは毎回驚くほど似ています。同僚のトラブルシューティングに何度も呼ばれて気づいたのは、知識の問題ではなく、人間の脳と cron の文法のすれ違いが原因ということ。代表的なパターンを並べます。

  1. 日と曜日の両指定で OR になる: 0 0 15 * 1 は「15 日」と「月曜」の どちらかに該当する日に実行されます。「15 日かつ月曜のときだけ」と勘違いすると、毎月 15 日 + 毎週月曜の両方で動いて月 5 回以上実行されてしまいます。詳細はセクション 9 のあとにも出ます。
  2. */3 は「3 分おき」ではなく「3 の倍数の分」: 結果的に 0, 3, 6, ..., 57 分に実行され、そこから 0 分に戻るまで 3 分空くのでほぼ「3 分おき」です。60 の約数でないステップ値(7、11 など)では端で不規則になります。
  3. 30 * * * * は「30 分おき」ではなく「毎時 30 分」: 一見 30 分おきに見えますが、これは「分フィールドが 30 のとき」という意味で、1 時間に 1 回しか走りません。30 分おきにしたいなら 0,30 * * * * または */30 * * * * と書きます。
  4. パスが通っていない: cron は対話シェルではないので、.bashrc.zshrc は読まれません。python と書いても見つからず、which python で出てきたフルパス /usr/local/bin/python を書く必要があります。ジョブの先頭で PATH=/usr/local/bin:/usr/bin:/bin を設定しておくと幸せになれます。
  5. 標準出力の捨て先: ジョブが何か出力するたびにメールが飛ぶのが cron のデフォルト挙動。MAILTO="" で無効化するか、>> /var/log/myjob.log 2>&1 で明示的にログファイルへリダイレクトしましょう。放置すると /var/mail/root が肥大化します。
  6. パーセント記号のエスケープ忘れ: cron の % は「ここから標準入力」という特殊な意味を持ちます。date +%Y-%m-%d をそのまま書くと %Y-%m-%d 以降が標準入力扱いになり無言で失敗します。\% にエスケープするか、スクリプトファイルに逃がすのが安全です。
  7. 環境変数の未設定: LANG, LC_ALL, HOME, USER あたりが設定されていないので、日本語を含むコマンドを書くと文字化けして失敗することがあります。必要な環境変数は crontab の先頭でまとめて設定するのが定石です。

1 番の「日と曜日の OR 動作」は特に重要なので、もう一度念押しします。Vixie cron のマニュアルにはっきり「両方を制限したときは OR」と書かれています。「15 日の月曜だけ」をやりたいなら、どちらかを * にしてスクリプト側で判定するしかありません。Quartz 系だと ? で片方を無効化する文化なのは、この曖昧さを避けるための設計でもあります。

cron 式のテスト方法

cron は「書いて、待って、失敗していることに翌日気づく」というループに入りがちです。書いた式を実行する前に検証する癖をつけると精神衛生に良いです。

いちばん手軽なのはオンラインの crontab.guru。式を貼り付けると「At 09:00 on every day-of-week from Monday through Friday.」のように英語で解説が出ます。自然言語で確認できるので、書いたつもりの意図とずれていないかすぐわかります。

コマンドラインで現在の crontab を確認するなら次のコマンドです。

# 自分の crontab 一覧表示
crontab -l

# 編集
crontab -e

# 一時ファイルから一括登録
crontab /tmp/my-crontab.txt

# 削除(確認なしなので注意)
crontab -r

systemd timer を使う場合は systemd-analyze calendar で次の発火時刻を確認できます。cron 式とは構文が違うので直接使えませんが、OnCalendar 式を検証する用途にはこれが決定版です。

$ systemd-analyze calendar "Mon *-*-* 09:00:00"
  Original form: Mon *-*-* 09:00:00
Normalized form: Mon *-*-* 09:00:00
    Next elapse: Mon 2026-04-20 09:00:00 JST
       From now: 4 days 21h left

AWS EventBridge なら、マネジメントコンソールに cron 式を貼り付けると「次回 10 回の実行日時」をプレビューしてくれます。aws events put-rule --schedule-expression "cron(0 9 ? * MON-FRI *)"--dry-run や検証環境で試すのも有効です。

手元で実際に動作確認したいときは、実ジョブではなく date を出力するだけのダミーを一時的に設定する方法も便利です。

# 毎分 date をログに書き出す(検証用)
* * * * * /bin/date >> /tmp/cron-test.log 2>&1

1 分待ってから tail -f /tmp/cron-test.log でタイムスタンプが増えていれば、crontab の登録と環境変数まわりは問題ないと判断できます。そのあと目的のジョブに差し替えれば、cron 側の問題か、ジョブ本体のエラーかの切り分けができます。

実行失敗の検知

cron の最大の弱点は「失敗しても誰も気づかない」こと。ジョブが exit code 1 で終わっても、そのままログに残るだけで次の発火を待ち、翌朝になって「昨晩のバックアップが動いてない」と青ざめる、というのは誰もが通る道です。外形監視を組み合わせましょう。

いちばん手軽なのが Healthchecks.ioDead Man's Snitch のような、いわゆる「逆向き監視」サービス。ジョブの最後に所定の URL に ping を送り、「期待した時間内に ping が来なかったら通知する」方式です。ジョブが途中で落ちても、そもそも cron が発火しなくても、どちらでも検知できるのが強みです。

# 成功時だけ ping を送るシンプル版
0 3 * * * /path/to/backup.sh && curl -fsS -m 10 --retry 5 -o /dev/null https://hc-ping.com/your-uuid

# 開始と終了を分けてログする版
0 3 * * * curl -fsS https://hc-ping.com/your-uuid/start && /path/to/backup.sh && curl -fsS https://hc-ping.com/your-uuid

Slack 通知を組み込むなら、Incoming Webhook の URL を叩くだけで十分です。成功時と失敗時でメッセージを分けるなら、シェルの終了ステータスをチェックします。

#!/bin/bash
# notify.sh
set -e
SLACK_URL="https://hooks.slack.com/services/XXX/YYY/ZZZ"

if /path/to/job.sh; then
  curl -s -X POST -H 'Content-type: application/json' \
    --data '{"text":"[OK] nightly job finished"}' "$SLACK_URL"
else
  curl -s -X POST -H 'Content-type: application/json' \
    --data '{"text":"[FAIL] nightly job EXIT='"$?"'"}' "$SLACK_URL"
  exit 1
fi

もうひとつ必ず入れてほしいのが 重複起動防止です。ジョブの実行時間が cron の間隔を超えると、次の発火が走ってしまって同じジョブが 2 本並行で動き出し、ファイルロックやデータ競合が起きます。Linux の flock がこの用途に向いています。

# /tmp/myjob.lock を排他ロックとして使う
*/5 * * * * flock -n /tmp/myjob.lock -c '/path/to/script.sh'

-n オプションを付けると「ロックが取れなかったら即座に諦めて終了」するので、前のプロセスが走っている間は新しいプロセスが走らなくなります。このワンライナーを入れておくだけで、運用で最も頻発する「2 重起動でデータ壊れた」系の事故がほぼ消えます。

ベンリーの cron ビルダー使い方

ここまで読んで「数字を覚えるよりボタンで選びたい」と思った方には、ベンリーの cron ビルダーが向いています。分・時・日・月・曜日のフィールドをそれぞれドロップダウンと入力欄で指定すると、対応する cron 式がその場で生成され、「人間の言葉での説明」が下に表示されます。式を手で書き換えたときもリアルタイムに解説が更新されるので、特殊文字の効果を実験するのにも使えます。

使い方はシンプルで、次の 3 ステップです。

  1. 「毎日 9 時」などの典型パターンをプリセットから選ぶ、または各フィールドを個別に設定。
  2. 右側のプレビュー欄で「次回 5 回の実行日時」を確認。タイムゾーンも切り替え可能。
  3. 「コピー」ボタンで cron 式をクリップボードへ。そのまま crontab -e に貼り付けるだけ。

AWS EventBridge 向けの 6 フィールド式にも切り替えられ、L? のような Quartz 特殊文字も入力できます。ブラウザ上で完結するので、サーバーに SSH する前に「本当にこの式で意図通りの時刻に動くのか」を確認してから本番投入できます。

よくある質問

毎月最終日を表現したい。Linux cron では無理?

標準の Vixie cron では単独では書けず、スクリプト側の判定が必要です。毎日 23:55 にスクリプトを発火させ、date -d tomorrow +%d の結果が 01 なら「今日が月末」として処理を走らせる、というのが定番パターンです。もし Quartz や AWS EventBridge を使えるなら L 特殊文字で「日」フィールドを指定すれば 1 行で済みます。55 23 L * ? のような書き方ですね。実装ごとに機能差があるので、環境にあわせて使い分けるのが正解です。

日と曜日を同時指定するとどうなる?

両方とも * 以外を指定した場合、Vixie cron は「どちらかにマッチ」という OR 動作になります。0 0 15 * 1 は「毎月 15 日」「毎週月曜」のどちらか、という意味になり、月 5〜6 回は実行されます。これを AND(「15 日かつ月曜」)にしたいなら、片方を * にして、スクリプト内で date +%udate +%d でもう片方を判定するしかありません。Quartz 系では ? で明示的に片方を無効化する文化になっており、この曖昧さを構文レベルで防いでいます。

AWS EventBridge はなぜ 6 フィールドなの?

AWS EventBridge は Amazon が Quartz Scheduler をベースに拡張した cron 実装を採用しており、Quartz が秒単位スケジューリングをサポートするため末尾に「年」フィールドが追加されています。フィールドは「分 時 日 月 曜日 年」の順で、Linux 標準の 5 フィールドをそのまま貼り付けると構文エラーになります。さらに「日」と「曜日」の同時指定が禁止されており、どちらか片方を必ず ? にしなければなりません。この「?」を忘れるのが移行時の定番エラーです。

JST で動かしているはずの cron が UTC で動く原因は?

ほぼ確実に「サーバーのタイムゾーンが UTC のまま」のパターンです。クラウド VM(EC2, GCE, Azure VM)のデフォルトタイムゾーンは UTC で、timedatectl で確認すると Time zone: UTC と出ます。解決策は 2 つ。システム全体を JST に変える(sudo timedatectl set-timezone Asia/Tokyo)か、crontab ファイルの先頭に TZ=Asia/Tokyo を書いてそのジョブだけ JST で動かす方法です。GitHub Actions のようにタイムゾーン指定ができない環境では、UTC 基準で時刻を計算して書くのが唯一の手です。

同じジョブが 2 回走ってしまう原因と対策は?

主な原因は 2 つ。1 つめは「前回の実行が終わる前に次回の発火が来る」ケース。例えば 5 分おきに設定したジョブの処理時間が 6 分かかると、必ず重複します。対策は flock -n /tmp/myjob.lock -c '...' でロックを取り、ロックが取れなければ即座に諦める方式が定番です。2 つめは「DST の切り替えで時刻が 2 回登場する」ケースで、America/New_York のような DST 地域特有です。こちらは cron を UTC で運用すれば回避できます。重複起動は実害が大きい(データ破損・二重課金)ので、クリティカルなジョブには必ず対策を入れてください。

cron 運用のベストプラクティス

1) タイムゾーンは UTC 固定にしてジョブ側で時差を吸収する、2) flock で重複起動を防ぐ、3) Healthchecks.io などで外形監視を入れる、4) 標準出力はログファイルにリダイレクトする、5) フルパスで書く、6) crontab の先頭で環境変数を明示する。この 6 つを守るだけで、cron 運用の事故は劇的に減ります。どれもたった 1 行ずつの対策ですが、効果は桁違いです。

cron 式をサクッと作りたいなら

ベンリーの cron ビルダーは、フィールドを選ぶだけで式を生成し、次回実行日時のプレビューと「人間の言葉での説明」をその場で表示します。AWS 6 フィールドへの切り替えやクリップボードコピーもワンクリックで OK。

cron ビルダーを開く →