cron の日と曜日は OR になる|第 2 月曜・月末・最終金曜・隔週の正しい書き方
「毎月第 1 月曜の 9 時」のつもりで「0 9 1-7 * 1」と書いたら、毎週月曜に加えて毎月 1〜7 日の毎日にも動いてしまった。cron でとても多いこの失敗は、日と曜日の両方を指定すると「どちらか」に当てはまる日に動くという、cron 独特のルールが原因です。この記事では、日と曜日が OR になる仕組みと例外、crontab で第 n 曜日・月末・最終金曜・隔週を正しく動かす書き方、AWS EventBridge や Spring で使える L・#・W の書き方までを、実際の実行日付の例つきで解説します。
日と曜日を両方書くと「どちらか」の日に動く
cron 式の 5 つの欄のうち、分・時・月は「すべての条件を満たす時刻」に動く、という素直な読み方をします。ところが、日(3 つ目)と曜日(5 つ目)だけは特別で、両方に * 以外の値を書くと「日の条件か、曜日の条件の、どちらかを満たす日」に動きます。これは Linux の cron(Vixie cron とその流れをくむ cronie など)の昔からの仕様で、GitHub Actions や Kubernetes の CronJob も同じ読み方をします。
たとえば「毎月第 1 月曜の 9 時」のつもりで書いた「0 9 1-7 * 1」は、「毎月 1〜7 日」または「毎週月曜」の 9:00 に動きます。2026 年 10 月 12 日(月)から数えると、実際の実行日は次のようになります。
| 実行日 | 当てはまった条件 |
|---|---|
| 10 月 12 日(月) | 曜日が月曜 |
| 10 月 19 日(月) | 曜日が月曜 |
| 10 月 26 日(月) | 曜日が月曜 |
| 11 月 1 日(日) | 日が 1〜7 日 |
| 11 月 2 日(月) | 日が 1〜7 日、曜日も月曜 |
| 11 月 3 日(火) | 日が 1〜7 日 |
第 1 月曜だけのつもりが、毎週月曜と毎月の最初の 7 日間に動くので、月に 10 回前後実行されます。定例会議のリマインドや月次レポートの送信をこの書き方で設定すると、毎週のように通知が飛ぶ事故になります。
例外:どちらかが * で始まると「両方」になる
さらにややこしいことに、Linux の cron には「日か曜日の値が * で始まっていると、OR ではなく AND(両方を満たす日)になる」という例外があります。「*/2」のように * から始まる間隔指定がこれに当たります。
| 式 | 読まれ方 | 実行される日 |
|---|---|---|
| 0 10 1-31/2 * 2 | OR(どちらか) | 奇数日の毎日+毎週火曜 |
| 0 10 */2 * 2 | AND(両方) | 奇数日のうち火曜日だけ |
「1-31/2」と「*/2」はどちらも 1・3・5…31 日を表すのに、書き方ひとつで実行日がまったく変わります。2026 年 10 月以降で「0 10 */2 * 2」の実行日を並べると、10 月 13 日・10 月 27 日・11 月 3 日・11 月 17 日・12 月 1 日…と、奇数日の火曜だけになります。この例外は実装の細かい挙動なので、ほかのスケジューラーに移したときに読み方が変わる可能性もあります。意図して使うより、「この書き方は避ける」と覚えておくほうが安全です。
第 n ○曜日を crontab で動かす書き方
crontab には「第 2 火曜」を直接表す書き方がありません。そこで、第 n 曜日は必ず「(n−1)×7+1 日 〜 n×7 日」の 7 日間のどこかにある、という性質を使います。第 1 なら 1〜7 日、第 2 なら 8〜14 日、第 3 なら 15〜21 日、第 4 なら 22〜28 日です。
- 1
日の欄で 7 日間に絞る
第 2 火曜の 10:00 なら、日の欄を 8-14、曜日の欄を * にして「0 10 8-14 * *」とします。曜日を 2 にすると OR になってしまうので、ここでは書きません。
- 2
コマンドの先頭で曜日を確かめる
date +%u は月曜を 1、日曜を 7 とする曜日の数字を返します。crontab では % をエスケープして「[ "$(date +\%u)" = 2 ] && /path/to/command」と書くと、火曜日のときだけ後ろのコマンドが実行されます。
- 3
次の実行日で確かめる
cron としては 8〜14 日の毎日動くので、コマンドが本当に第 2 火曜だけ実行されるかを、スクリプトのログや手動のテストで確認しておきます。
AWS EventBridge と Spring の @Scheduled なら、# を使って直接書けます。EventBridge は曜日の数字が 1(日曜)から始まるので、第 2 火曜の 10:00 は「cron(0 10 ? * 3#2 *)」、Spring なら「0 0 10 ? * TUE#2」です。EventBridge の書き方で 2026 年 10 月以降を並べると、10 月 13 日・11 月 10 日・12 月 8 日・1 月 12 日と、毎月の第 2 火曜だけになります。
毎月末日・最終金曜日の書き方(31 日指定の落とし穴)
「毎月末日に締め処理」を「0 23 31 * *」と書くのは典型的なミスです。31 日がない 2・4・6・9・11 月には一度も動きません。同じように、30 日を指定すると 2 月に動かず、「0 9 30 2 *」(2 月 30 日)のように存在しない日付を指定すると、永遠に動かない式になります。
| やりたいこと | crontab | EventBridge | Spring |
|---|---|---|---|
| 毎月末日 23:00 | 0 23 28-31 * * +末日チェック | cron(0 23 L * ? *) | 0 0 23 L * * |
| 月末の 3 日前 | 0 9 25-28 * * +日付チェック | cron(0 9 L-3 * ? *) | 0 0 9 L-3 * * |
| 最終金曜 18:00 | 0 18 22-31 * * +曜日と週のチェック | cron(0 18 ? * 6L *) | 0 0 18 ? * 5L |
| 最終平日 | 0 18 26-31 * * +平日チェック | cron(0 18 LW * ? *) | 0 0 18 LW * * |
| 15 日に一番近い平日 | 書けない(コマンド側で判定) | cron(0 9 15W * ? *) | 0 0 9 15W * * |
crontab で末日を判定するには、「明日が 1 日なら今日は末日」という考え方を使います。GNU の date(一般的な Linux)なら「0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /path/to/command」と書けば、28〜31 日のうち末日だけコマンドが動きます。最終金曜なら、日を 22-31 に絞ったうえで「今日が金曜」かつ「7 日後が来月」であることを確かめます(例: [ "$(date +\%u)" = 5 ] && [ "$(date -d '+7 days' +\%m)" != "$(date +\%m)" ] && コマンド)。
EventBridge の L は「その月の末日」、6L は「最終金曜(6 が金曜)」、15W は「15 日に一番近い平日」です。たとえば 15W を 2026 年 11 月で計算すると、15 日が日曜なので翌日 16 日(月)に動きます。W は月をまたがないので、1W が土曜なら 3 日(月)になります。
隔週・2 週に 1 回は cron だけでは書けない
「隔週月曜」は、cron の 5 つの欄では表せません。日の欄の「*/14」は「1・15・29 日」を指すだけで、月が変わるとまた 1 日から数え直すため、2 週間おきにはなりません。週番号の欄もないので、コマンド側で判定するのが定番です。
- 毎週月曜に動かし、ISO 週番号が偶数の週だけ実行する:0 9 * * 1 [ $(( $(date +\%-V) \% 2 )) -eq 0 ] && コマンド(%-V は 08 のような先頭の 0 を外す指定。0 つきの 08・09 はシェルの計算で 8 進数として読まれてエラーになる)
- 週番号は年をまたぐと 52 または 53 から 1 に戻るため、53 週ある年の年末年始は 2 週連続や 3 週空きになることがある
- 厳密に 14 日おきにしたいなら、基準日からの経過日数を計算し、14 で割り切れる日だけ実行する(例: 基準日の UNIX 時間との差を 86400 で割る)
- ジョブ側で前回の実行日をファイルに記録し、13 日以上たっていたら実行する、という方法も確実
書き方ごとの違いまとめと、確かめ方
日と曜日の扱いは、cron を使う仕組みによって違います。同じ式でも移し替えた先で意味が変わることがあるので、どの仕組みで動かすのかを先に確認しておきましょう。
| 仕組み | 日と曜日を両方書いたとき | L・#・W |
|---|---|---|
| crontab(Linux) | どちらか(OR)。* 始まりなら両方(AND) | 使えない |
| GitHub Actions | crontab と同じ | 使えない |
| Kubernetes CronJob | crontab と同じ | 使えない |
| AWS EventBridge | 両方は書けない。片方を ? にする | 使える |
| Spring @Scheduled | 両方(AND) | 使える |
| systemd timer | 両方(AND) | ~ で末日から数える書き方がある |
Spring では「0 0 9 1-7 * MON」と書けば、そのまま「毎月 1〜7 日のうち月曜日」、つまり第 1 月曜になります。crontab の感覚で書くと逆の意味になるので、移植するときは特に注意が必要です。EventBridge の書き方はEventBridge の cron 式の書き方で詳しく解説しています。
cron 式チェッカーでは、日と曜日の両方を指定すると「どちらかに当てはまる日に動きます」と警告し、* 始まりで AND になる場合もそれを知らせます。31 日だけの指定で動かない月がある、存在しない日付で一度も動かない、といったミスも検出し、次の実行日付を並べて確認できます。crontab の基本的な書き方はcrontab の書き方と早見表、GitHub Actions で日本時間にしたい場合はGitHub Actions の cron を日本時間で動かす方法もあわせてどうぞ。
よくある質問
- cron で毎月第 2 月曜日に実行するにはどう書きますか?
- crontab では日の欄を 8-14、曜日の欄を * にして「0 9 8-14 * *」とし、コマンドの先頭で月曜かを確かめます([ "$(date +\%u)" = 1 ] && コマンド)。曜日の欄に 1 を書くと OR になり、毎週月曜にも動いてしまいます。EventBridge なら cron(0 9 ? * 2#2 *)、Spring なら 0 0 9 ? * MON#2 で直接書けます。
- cron で毎月末日に実行する方法は?
- crontab には末日を表す書き方がないので、日の欄を 28-31 にして毎日動かし、「明日が 1 日なら実行」という判定をコマンドに付けます([ "$(date -d tomorrow +\%d)" = "01" ] && コマンド)。EventBridge や Spring なら日の欄に L と書くだけで末日になります。31 と書くと 31 日がない月は動きません。
- cron の日と曜日はなぜ AND ではなく OR なのですか?
- 昔の Unix の cron からの仕様で、「毎月 1 日と 15 日、それに毎週金曜にも動かす」のような指定を 1 行で書けるように、日と曜日を両方書くと OR になると決められています。互換性のため、Linux の cron や GitHub Actions、Kubernetes でも同じ読み方が続いています。Spring や systemd では AND です。
- cron で隔週(2 週間に 1 回)実行できますか?
- cron の式だけでは書けません。毎週動かしたうえで、コマンド側で週番号の偶数・奇数を判定する(date +\%V を 2 で割った余りを見る)か、前回の実行日を記録して 14 日たったかを確かめる方法を使います。日の欄の */14 は 1・15・29 日を表すだけで、2 週間おきにはなりません。
この記事で紹介したツール
あわせて読みたい
cron が動かない原因チェックリスト|手動では動くのに失敗する理由とログの確認方法
crontab に書いたはずのバッチが動いていない。ターミナルで手で実行すると成功するのに、cron からだと何も起きない。cron のトラブルは、エラーがどこにも表示されないまま静かに失敗することが多く、原因を探すのに時間がかかりがちです。この記事では、cron が動かないときに確かめる順番を「式」「cron 本体」「ログ」「実行環境」「ファイルと権限」「タイムゾーン」に分けて整理し、手動では動くのに cron だと失敗する典型的な理由と、その直し方をまとめます。
記事を読む →crontab の書き方と早見表|5 つの欄と記号の意味・よく使う例・登録のコマンド
「毎日 3 時にバックアップ」「5 分ごとに監視スクリプト」のような定期実行は、Linux では crontab に 1 行書くだけで設定できます。ただ、「分 時 日 月 曜日」の 5 つの欄と、* , - / という記号の組み合わせは、慣れないうちは読み間違えやすく、1 時間に 60 回動いてしまったり、7 分おきのつもりが 4 分おきになったりといった事故も起きがちです。この記事では、crontab の書き方の基本と記号の意味、そのまま使える早見表、crontab -e・-l・-r の使い方、コマンド部分を書くときの注意点までを、例を中心に解説します。
記事を読む →EventBridge の cron 式の書き方|? の意味・曜日の数字・日本時間の指定と crontab からの書き換え
AWS で Lambda や ECS のタスクを定期実行するときに使う EventBridge の cron 式は、Linux の crontab と似ているようで、欄の数・? の使い方・曜日の数字・タイムゾーンが少しずつ違います。crontab の感覚で「0 9 * * 1-5」と書くとエラーになり、曜日の数字をそのまま移すと 1 日ずれ、時刻は UTC で読まれて 9 時間ずれる、とつまずきどころが重なります。この記事では、EventBridge の cron 式の 6 つの欄と ? のルール、曜日の数字、L・W・# の使い方、rate 式との違い、ルールと Scheduler での日本時間の扱い、crontab・Kubernetes・Spring・systemd との書き換え表までをまとめます。
記事を読む →