メインコンテンツへスキップ
ブログ一覧に戻る
Web制作・運用

cronで「毎月1日の月曜だけ」は書けない|日と曜日はANDではなくORになる

2026年10月30日
15分で読めます

この記事でわかること

cronの式で日と曜日の両方を指定すると、絞り込みではなく「どちらかに一致した日」になります。POSIXが定める4つの場合分けと、実装側マニュアルが載せている具体例から、この1組だけが例外になる理由を確認日つきで整理し、式で表現できない条件をどこで判定するかの判断軸を示します。

「毎月1日が月曜日のときだけ実行したい」。そう考えて、日フィールドに 1、曜日フィールドに 1 を書く。0 9 1 * 1 のような式になります。

この式は、意図したとおりには動きません。規定どおりに読むと、毎月1日の9時と、毎週月曜の9時の両方で実行されます。絞り込んだつもりの指定が、対象日を増やしています。

これは実装の癖ではなく、仕様にそう書かれています。この記事では、POSIX の crontab と、Linux で広く使われる実装のマニュアルを突き合わせて、なぜこの1組だけが例外になるのかを確認します。扱わないのは、cron 以外のスケジューラ(それぞれ別の規定を持ちます)と、時刻のずれに関わる挙動です。参照したのは POSIX Issue 7 の crontab、POSIX Issue 8 の crontab、crontab(5)(Linux man-pages 版) の3件で、いずれも2026年10月29日に取得しました。

30秒で要点

  • POSIX は日付の指定を4つの場合に分けて規定しており、「どちらかに一致」になるのは月または日と曜日の両方が指定された場合だけである
  • この規定は旧版に限った話ではない。現行版でも同じ文言が保たれている
  • 実装側のマニュアルも同じ挙動を書き、"30 4 1,15 * 5" を「毎月1日と15日の4:30、加えて毎週金曜」と自ら例示している
  • 曜日フィールドの範囲は、参照した情報源のあいだで一致していない(0〜6と0〜7)。使っている実装のマニュアルを見る必要がある
  • ステップ値も同様に「フィールドの中だけ」で評価される。*/23 を時フィールドに書いても23時間間隔にはならない
  • 式で表現できない条件は、スケジュールの側で無理に表すより、判定をコマンド側に置くほうが扱いやすい

POSIXは日付の指定を「4つの場合」に分けている

まず、フィールドの構成を確認します。POSIX の crontab は、1行を6フィールドで構成し、最初の5フィールドを整数パターンとして規定します。範囲は分が0〜59、時が0〜23、日が1〜31、月が1〜12、曜日が0〜6(0が日曜)です。6番目のフィールドは、指定した時刻に sh によって実行される文字列です。

重要なのは、この5つが対等に並んだ条件ではないという点です。規定は「日付の指定は2つのフィールドで行える」と前置きしてから、4つの場合に分けます。

  1. 月・日・曜日がすべてアスタリスクの場合:毎日が対象になる
  2. 月または日が要素またはリストで指定され、曜日がアスタリスクの場合:月と日のフィールドが対象日を定める
  3. 月と日の両方がアスタリスクで、曜日が要素またはリストの場合:指定された曜日だけが対象になる
  4. 月または日と、曜日の両方が指定された場合:次の規定が効く

4番目の条文が、この記事の主題です。

Finally, if either the month or day of month is specified as an element or list, and the day of week is also specified as an element or list, then any day matching either the month and day of month, or the day of week, shall be matched.

「月と日に一致する日」または「曜日に一致する日」のいずれかに一致した日が対象になる。原文に "both" は現れず、"either" と書かれています。ここだけが、条件を並べると対象が広がる場所です。

条文の主語にも注意が必要です。曜日と組み合わさる相手は日だけではなく、月も含みます。「月または日が指定され、かつ曜日も指定されたら」という条件です。

なお、この規定は旧版に固有のものではありません。Issue 7 のページには「より新しい版がある」という案内がありますが、その新版(Issue 8 / IEEE Std 1003.1-2024)でも、フィールドの一覧・4つの場合分け・上に引いた一文は、文字単位で一致しています。「古い規定を読んでいたのではないか」という疑いは、ここで消えます。 比較したのは crontab のページのみで、他のユーティリティの差分は確認していません。

実装側のマニュアルも同じことを書き、例まで載せている

仕様がそう書いていても、実装が追従しているかは別の問いです。Linux で広く使われる cronie の crontab(5) マニュアルも、同じ内容を注記しています。

Note: The day of a command's execution can be specified in the following two fields — 'day of month', and 'day of week'. If both fields are restricted (i.e., do not contain the "" character), the command will be run when either field matches the current time.

そして、このマニュアルは具体例を自ら載せています。

For example, "30 4 1,15 5" would cause a command to be run at 4:30 am on the 1st and 15th of each month, plus every Friday.

"30 4 1,15 * 5" は、毎月1日と15日の4:30に加えて、毎週金曜の4:30に実行される。この例はマニュアルに書かれているものであり、本記事で実機に登録して確認した結果ではありません。 自分で例を作るより、マニュアルが自ら示している例のほうが根拠として強いので、そのまま引いています。

一方で、この注記には読み切れない部分があります。「制限されている(restricted)」の定義は「アスタリスクを含まないこと」です。では */2 のようにアスタリスクを含む式は、どちらに分類されるのか。この一文からは読み取れません。使っている実装でこの形を書く場合は、挙動を自分で確かめる必要があります。

フィールドの範囲は、情報源のあいだで一致していない

もう1つ、突き合わせて初めて見える差があります。曜日フィールドの範囲です。

POSIX は0〜6で、0が日曜です。cronie のマニュアルの表は、分が0〜59、時が0〜23、日が1〜31、月が1〜12(または名前)、曜日が0〜7(0または7が日曜、または名前)と書いています。名前は曜日・月の先頭3文字で、大文字小文字は問いません。名前の範囲とリスト(mon,wed,fri、jan-mar など)も許されます。

この差をどう扱うか。本記事は、どちらかが誤りだという評価をしません。 確認したのは「両ページの記述が異なる」という事実だけです。評価するには、どの版のどの文書を基準にするかを先に決める必要があり、今回それをしていません。

実務上の意味は別のところにあります。曜日の 7 は、参照する文書によって「有効な日曜」にも「範囲外」にもなる。 式を書き写して別の環境に持っていくとき、この1文字が差分になります。

ステップ値も「フィールドの外」には効かない

日と曜日の関係だけが例外なら、それだけ覚えれば済みます。ただ、同じ性質の落とし穴がもう1つあります。

cronie はステップ値(/<数値>)を持ちますが、マニュアルはステップが適用したフィールドの中だけで評価されると明記しています。

Please note that steps are evaluated just within the field they are applied to. For example "*/23" in hours field means to execute the job on the hour 0 and the hour 23 within a calendar day.

*/23 を時フィールドに書くと「23時間ごと」ではなく、同じ暦日の0時と23時に実行されます。マニュアルは分フィールドの */35 についても同趣旨を書いています(35分ごとではなく、毎時0分と35分の2回)。

ステップ値もランダム指定も cronie の拡張で、POSIX の規定には含まれません。並べて見ると、日と曜日の件と共通する構造が見えます。cron の式は、「時間軸上の間隔」を書く言語ではなく、「各フィールドが一致するかどうか」を書く言語である。 間隔として読むと、どちらの件でも読み違えます。

なぜ「条件を足すほど絞られる」と読んでしまうのか

規定を知っても、最初は絞り込みとして読みます。この誤解が強いのは、5つのフィールドのうち4つでは、その読み方が実際に正しいからです。

分・時・月を足していくと、対象は確かに狭まります。0 9 * * * に月を足して 0 9 * 4 * にすれば4月だけになる。この経験が繰り返されると、「フィールドは条件であり、条件は重なるほど厳しくなる」という理解ができます。そして4フィールド分は、その理解を裏切りません。

もう1つは、該当する書き方がまれだと考えられることです。日と曜日を同時に指定する式を書くのは、「毎月1日の月曜だけ」のように絞り込みたい動機があるときに限られます。つまりこの規定に出会うのは、絞り込むつもりで書いた瞬間だけ。反証が来るタイミングが、最も反証を受け入れにくいタイミングと重なっています。

さらに、間違いが静かです。式そのものは規定に沿っているため、書き間違いとして差し戻される形にはなりません。意図した日にはきちんと実行されているので、しばらくは成功しているように見えます。気づくのは、意図していない日の実行が別の問題を起こしたときです。

式で表現できない条件は、どこで判定するか

では「毎月1日の月曜だけ」をどう実現するか。cronie のマニュアルは、フィールドだけでは表現できない間隔について、解決の形を示しています。

For more fine-grained control you can do something like this:

続く例は、毎分起動する式(* * * * *)に、date コマンドの出力を使って条件を判定するシェルの条件分岐を組み合わせたものです。58分ごと・23時間ごとといった、ステップ値では書けない間隔を実現する例として置かれています。

ここで引用できるのは、手法の形までです。 このマニュアルの例が扱っているのはステップ値の制約であって、日と曜日の関係ではありません。したがって「日と曜日の AND を書けないときの公式な回避策」として示すことはできません。示せるのは、cron の式で表現できない条件は、スケジュールの側ではなくコマンド側で判定する形がマニュアルに置かれているという範囲までです。

この形を採ると、設計上の判断が1つ必要になります。頻繁に起動して毎回条件を判定する構成は、起動回数そのものが増えます。実行コストが無視できない処理では、起動を軽い判定だけに留め、本処理を分けることになります。何回起動して何回空振りするかを、先に見積もる対象として扱うのが妥当です。

判断軸:この式は、いくつの集合の和集合か

点検の順序として、次を提案します。

  1. 日フィールドと曜日フィールドの両方がアスタリスク以外になっている行を探す。 片方がアスタリスクなら、絞り込みとして読んで差し支えありません。両方が埋まっている行だけが、和集合として読む必要のある行です
  2. 該当行を、2つの文に書き直す。 「毎月1日と15日の4:30」「毎週金曜の4:30」のように、1つの式を2つの日本語の文に分けます。分けて書けない式は、読み違えている可能性があります
  3. 意図した集合と比べる。 増えた側が許容できるかを判断します。許容できないなら、式では表せない条件です
  4. 判定をどこに置くかを決める。 式で表せない条件は、起動側の頻度を上げてコマンド側で判定するか、スケジュールの表現力が異なる別の仕組みを検討するかの選択になります。本記事は後者の各方式を確認していないため、ここでは選択肢の存在を示すに留めます
  5. 曜日の数字の表記を揃える。 7 と 0 のどちらを使うかは、持ち込む先の実装のマニュアルで確認してから決めます

この順序の要点は、2番目を飛ばさないことです。式のまま見ていると、和集合は見えません。日本語の文に開いた瞬間に、「毎週金曜」が意図に入っていなかったことが分かります。

手元で確かめられる最小の手順

いま運用している crontab があるなら、該当行があるかは短い手順で数えられます。

  1. 登録されている crontab の内容を取り出す(環境ごとに取得方法が異なるため、ここでは手順を限定しません)
  2. コメント行と空行を除き、3番目のフィールドと5番目のフィールドの両方がアスタリスクを含まない行を抜き出す
  3. 抜き出した各行を、前節の手順2のとおり2つの文に書き直す

数えるなら、次の指標になります。

  • 分子:日フィールドと曜日フィールドの両方がアスタリスクを含まない行の数
  • 分母:コメント行と空行を除いた crontab の行数
  • 単位:%(行数/行数)

この比率が高いか低いかに意味はありません。分子が1以上であることが分かれば十分です。その1行が、意図より多い日に実行されている可能性のある行だからです。0なら、少なくとも日と曜日の件については心配がないと言えます。

書き直しの作業そのものが検証になります。2つの文に分けてみて、片方が「そんなつもりはなかった」と読めたなら、それが見つけたかったものです。

確かなことと、確かでないこと

確かなこと(2026年10月29日に情報源を取得して確認した範囲):POSIX のフィールド構成と範囲の記述。日付の指定を4つの場合に分ける規定と、月または日と曜日の両方が指定された場合に「どちらかに一致」となる条文。その条文が Issue 7 と Issue 8 で文字単位で一致すること。cronie のマニュアルが同じ挙動を注記し、"30 4 1,15 * 5" の例を自ら示していること。cronie の曜日範囲が0〜7であること。ステップ値がフィールド内だけで評価され、*/23 が0時と23時になること。フィールドで表せない条件をコマンド側で判定する形がマニュアルに置かれていること。

確かでないこと:*/2 のようにアスタリスクを含む式が「制限されている」に当たるかどうか(参照したマニュアルの記述からは読み取れません)。POSIX と cronie の曜日範囲の差について、どちらが基準かという評価(今回していません)。cron 以外のスケジューラの挙動(それぞれの公式文書を別途確認する必要があります)。

本記事が踏み込んでいないこと:時刻のずれに関わる挙動。参照した3件にその記述がありません。時刻の前提そのものの扱いはタイムゾーンはUTCオフセットではないで別に書いています。また「意図した日以外に何回余分に実行されるか」という回数も示しません。月ごとの曜日の並びに依存し、参照したどの文書にも書かれていないためです。

定期実行を設計するときに立ち返る問い

  1. この式は、いくつの集合を指しているか。 1つだと思っている式が2つを指していることがあります
  2. この条件は、スケジュールの表現力の中に収まるか。 収まらない条件を式で表そうとすると、たいてい対象が広がる方向に壊れます
  3. 判定を、どこに置くか。 スケジュール側に置くか、コマンド側に置くか。この問いを先に決めておくと、式を複雑にする方向に進みにくくなります

3番目は、定期実行に限った話ではありません。自動化の設計では、「いつ動くか」と「動いたときに何を確かめるか」を分けて考えると見通しが立ちます。その切り分けはAI自動化のワークフロー設計で扱っています。また、起動回数が増える構成を選ぶときに何を見積もるかという観点はリトライは負荷の判断であると、定期実行をパイプラインに組み込む前提についてはCI/CD入門が近い論点を扱っています。

参考資料・引用元

よくある質問(FAQ)