
crontabの書き方入門 - cronの5つのフィールドと、動かないときに見る実行環境・ログ・二重起動
当サイトは Amazon.co.jp を宣伝しリンクすることで紹介料を得る手段を提供する、Amazonアソシエイト・プログラムの参加者です。価格・在庫はリンク先の最新情報をご確認ください。
crontabは、「毎朝4時にバックアップを取る」「15分ごとにキャッシュを消す」のように、決まった時刻にコマンドを動かすための設定ファイルです。1行に「分 時 日 月 曜日 コマンド」を書くだけで済むので、Linuxサーバーの定期実行では今も一番よく使われています。
書き方そのものは単純ですが、「ターミナルでは動くのにcronだと動かない」「金曜日だけのつもりが毎月1日にも動いた」といったつまずきが多い仕組みでもあります。この記事では、書き方の基本から、動かないときに確かめる場所までを順に整理します。
NOTE
仕様は2026年11月2日に、crontab(5)(cronie)、Debianのcrontab(5)、POSIXのcrontabなどのmanページで確認しました。動作はUbuntu 24.04(cron 3.0pl1-137ubuntu3)とmacOSで確かめています。
cronとcrontabの関係
用語が似ているので、最初に整理します。
| 名前 | 正体 | 役割 |
|---|---|---|
| cron(crond) | 常駐するデーモン | 1分ごとに設定を調べ、時刻が一致したコマンドを実行する |
| crontab(ファイル) | 設定ファイル | 「いつ、何を実行するか」を1行ずつ書く |
| crontab(コマンド) | 設定を編集するコマンド | crontab -e で編集、crontab -l で表示する |
cronにはいくつか実装があり、細かい挙動が違います。この記事では、よく出会う次の3つを扱います。
| 実装 | 使われている環境 | 元になったもの |
|---|---|---|
| Debianのcron | Debian、Ubuntu | Paul VixieのVixie cron 3.0に、Debianの修正を重ねたもの |
| cronie | RHEL、Fedora、Amazon Linuxなど | Vixie cronから派生(man7.orgのcrontab(5)はcronieのもの) |
| macOSのcron | macOS | Vixie cron系。ただしmanページは、launchdを使うよう案内している |
どれもVixie cronの流れをくんでいるので、基本の書き方は共通です。違いが出るところは、その都度書きます。
1行の書き方: 5つのフィールドとコマンド
crontabの1行は、空白で区切った5つの時刻のフィールドと、実行するコマンドでできています。
┌──────── 分(0-59)
│ ┌────── 時(0-23)
│ │ ┌──── 日(1-31)
│ │ │ ┌── 月(1-12、またはjan-dec)
│ │ │ │ ┌ 曜日(0-7、0と7が日曜日。またはsun-sat)
│ │ │ │ │
30 4 * * * /usr/local/bin/backup.shこの例は「毎日4時30分に /usr/local/bin/backup.sh を実行する」という意味です。分、時、月が現在時刻に一致し、日か曜日の条件(後述)を満たしたときに実行されます。cronは1分ごとに設定を調べるので、秒の単位は指定できません。
各フィールドには、次の書き方ができます。
| 書き方 | 意味 | 例 | 例の意味 |
|---|---|---|---|
* | そのフィールドのすべての値 | 時に * | 毎時 |
| 数字 | その値だけ | 分に 30 | 30分 |
a-b | aからbまで(両端を含む) | 時に 9-17 | 9時から17時まで |
a,b,c | 列挙した値 | 分に 0,30 | 0分と30分 |
*/n | 最初から最後までn刻み | 分に */15 | 0、15、30、45分 |
a-b/n | aからbまでn刻み | 時に 9-17/2 | 9、11、13、15、17時 |
| 名前 | 月と曜日は英語の3文字でも書ける(大文字と小文字は区別しない) | 曜日に mon-fri | 月曜から金曜 |
範囲と列挙は組み合わせられます(例: 1-5,10,20-25)。月と曜日の名前も、範囲や列挙に使えます(例: mon,wed,fri、jan-mar)。
刻み(/)はフィールドの中だけで数える
/ の刻みは、そのフィールドの範囲の中だけで数えます。たとえば分に */35 と書いても35分おきにはなりません。0分と35分、つまり1時間に2回の実行になります。cronieのcrontab(5)にも、この点が注意として書かれています。60を割り切れない間隔(7分おき、35分おきなど)は、cronの書き方だけでは表せません。
分に 20/15 のような「範囲のない開始値/間隔」を書いたときの扱いは、実装によって違います。GitHub Actionsのドキュメントは「20分から59分まで15分おき(20、35、50分)」と説明していますが、Debianのcronやcronieで同じ意味になるかは確かめられませんでした(未確認)。どの環境でも同じ意味にしたいときは、20-59/15 のように範囲を書きます。
よく使う書き方の例
| 書き方 | 意味 |
|---|---|
* * * * * | 毎分 |
*/5 * * * * | 5分おき |
0 * * * * | 毎時0分 |
0 9 * * 1-5 | 平日(月〜金)の9時 |
0 0 1 * * | 毎月1日の0時 |
30 3 * * 0 | 毎週日曜日の3時30分 |
0 9-18 * * 1-5 | 平日の9時から18時まで、毎時0分 |
0 0 1 1 * | 毎年1月1日の0時 |
下のデモに式を入力すると、各フィールドがどの値を表すかと、次に実行される5回の時刻を確かめられます。式の解釈は、Debianのcronやcronieと同じVixie cron系に合わせています。
| 分 | 0 | 0 |
| 時 | 9 | 9 |
| 日 | * | すべて |
| 月 | * | すべて |
| 曜日 | 1-5 | 月、火、水、木、金 |
日と曜日を両方指定するとOR(どちらか)になる
一番の落とし穴がここです。日(3番目)と曜日(5番目)の両方に値を書くと、「両方に一致した日」ではなく「どちらかに一致した日」に実行されます。
# 毎月1日と15日、それに加えて毎週金曜日の4時30分に実行される
30 4 1,15 * 5 /usr/local/bin/report.shcrontab(5)の例そのもので、cronieとDebianのどちらのmanページにも「1日と15日、さらに毎週金曜日」と書かれています。「13日の金曜日だけ」のように両方を満たす日を指定したいときは、cronの書き方だけではできません。曜日だけをcronで指定し、日付はコマンドの中で調べます。
# 8日から14日のうち、土曜日(date +%u が 6)のときだけ実行する
0 4 8-14 * * test $(date +\%u) -eq 6 && echo "2nd Saturday"もう1つ細かい違いがあります。Debianのcrontab(5)によると、Debianのcronは、日か曜日の「最初の文字が * かどうか」だけでORにするかを決めます。そのため 0 0 */2 * sun は、日が * で始まるのでORにならず、「奇数日の日曜日」だけに実行されます(*/2 は1、3、5…日)。POSIXの定めどおりなら、この書き方は「日曜日と奇数日」になる、とmanページは説明しています。デモの「0 0 */2 * sun」のボタンで、実際に選ばれる日を確かめられます。
@で始まる短縮表記
5つのフィールドの代わりに、@ で始まる短縮表記も使えます。
| 表記 | 意味 | 同じ意味の式 |
|---|---|---|
@reboot | cronデーモンの起動時に1回 | なし |
@yearly、@annually | 年に1回 | 0 0 1 1 * |
@monthly | 月に1回 | 0 0 1 * * |
@weekly | 週に1回 | 0 0 * * 0 |
@daily | 1日に1回 | 0 0 * * * |
@midnight | @daily と同じ(Debianのcronのmanページにはあるが、cronieのmanページにはない) | 0 0 * * * |
@hourly | 1時間に1回 | 0 * * * * |
@reboot について、Debianのcrontab(5)は「起動時」とはcronデーモンが起動した時刻のことで、ほかのデーモンよりも前に動く場合があると注意しています。ネットワークやデータベースが使える前提のコマンドは、@reboot で動かすと失敗することがあります。短縮表記はどれも拡張機能で、POSIXには定められていません。GitHub Actionsも短縮表記には対応していません。
crontab -eと/etc/crontabの違い
crontabには、ユーザーごとのものと、システム全体のものがあります。書き方が少し違うので注意します。
| 種類 | 場所 | 編集の仕方 | 書き方 |
|---|---|---|---|
| ユーザーのcrontab | /var/spool/cron/ の下(直接は触らない) | crontab -e | 5つのフィールド + コマンド |
| システムのcrontab | /etc/crontab | rootでファイルを直接編集する | 5つのフィールド + ユーザー名 + コマンド |
| パッケージなどの追加分 | /etc/cron.d/ の下のファイル | rootでファイルを置く | 5つのフィールド + ユーザー名 + コマンド |
システムのcrontabには、6番目に「どのユーザーで実行するか」を書く欄があります。ユーザーのcrontabにユーザー名を書くと、ユーザー名がコマンドの一部と見なされて失敗します。逆に、/etc/cron.d のファイルにユーザー名を書き忘れると、コマンドの先頭の単語がユーザー名として扱われます。
# 分 時 日 月 曜日 ユーザー コマンド
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
15 3 * * * www-data /usr/local/bin/cleanup-cache.sh >> /var/log/cleanup-cache.log 2>&1crontab コマンドの主なオプションは次のとおりです。
| コマンド | 動き |
|---|---|
crontab -e | 自分のcrontabをエディタで開く。保存して閉じると反映される |
crontab -l | 自分のcrontabを表示する |
crontab -r | 自分のcrontabを削除する(確認なし) |
crontab -ri | 削除する前に確認する |
crontab ファイル名 | ファイルの中身でcrontabを置き換える |
crontab -u ユーザー名 -e | ほかのユーザーのcrontabを編集する(root) |
crontab -T ファイル名 | 書き方を検査するだけで、反映しない(cronieのみ) |
-e と -r はキーボードで隣にあるため、-r を打ち間違えてcrontabを消してしまう事故が起きがちです。cronieは、編集や削除の前の内容を ~/.cache/crontab/crontab.bak に残します(cronieのcrontab(1))。Debianのcronにはこの機能がないので、crontab -l > ~/crontab.backup のように自分で控えを取っておくと安心です。
Debian系の/etc/cron.dはファイル名にドットを使えない
Debianのcron(8)によると、/etc/cron.d のファイル名は英字、数字、アンダースコア、ハイフンだけにする必要があります。ドットを含むファイル(backup.cron など)は無視されます。パッケージの更新で残る .dpkg-old のようなファイルを実行しないための仕組みです。ファイルの所有者はrootで、グループやほかのユーザーが書き込めない権限にする必要もあります。
/etc/cron.daily のようなディレクトリに置くスクリプトも、同じ名前の規則に従います。こちらは run-parts が順に実行するので、実行権限も必要です。
最後の行に改行を入れる
cronieのcrontab(5)には、crontabの最後の行が改行で終わっていないと、crontabが壊れていると見なされるという注意があります。エディタで編集するときは、最後の行の後ろに改行があることを確かめます。
ターミナルでは動くのにcronだと動かない理由
cronで一番多いトラブルは、実行環境の違いです。cronのコマンドは、ログインしたときのシェルとは違う、最小限の環境で動きます。
| 項目 | cronでの値 | 出典 |
|---|---|---|
SHELL | /bin/sh(Debianは /usr/bin/sh)。crontabの中で上書きできる | crontab(5) |
HOME、LOGNAME | /etc/passwd の値。HOME は上書きでき、LOGNAME はできない | crontab(5) |
PATH | /usr/bin:/bin | Ubuntu 24.04とmacOSのcronのバイナリにある既定値で確認 |
.bashrc や .profile | 読み込まれない | SHELL が /bin/sh で、ログインシェルとして起動しないため |
PATH が /usr/bin:/bin しかないため、/usr/local/bin やHomebrew、nvm、pyenvなどで入れたコマンドは、名前だけでは見つかりません。対策は3つあります。
- コマンドを絶対パスで書く(
/usr/local/bin/nodeなど) - crontabの先頭で
PATH=を書く - 実行するスクリプトの中で、必要な環境変数を設定する
crontabの中の環境変数の行では、変数が展開されません。Debianのcrontab(5)は、PATH=$HOME/bin:$PATH や PATH=~/bin:/usr/bin は思ったとおりに動かないと明記しています。値はそのまま書きます。
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=""
0 4 * * * /home/sample/bin/backup.sh >> /home/sample/log/backup.log 2>&1環境変数と秘密情報の扱いは、環境変数と.envによる設定と秘密情報の管理でも整理しています。
%は改行に変わる
crontabのコマンドの中の % は、改行に変わります。最初の % より後ろは、コマンドの標準入力として渡されます(POSIXでも同じ定めです)。そのため、date +%Y%m%d をそのまま書くと、日付の書式が途中で切れて失敗します。
# 失敗する: % で行が切れる
0 0 * * * tar czf /backup/site-$(date +%Y%m%d).tar.gz /var/www
# 動く: \% と書く
0 0 * * * tar czf /backup/site-$(date +\%Y\%m\%d).tar.gz /var/www% を何度も使う処理は、スクリプトのファイルにまとめてcrontabからはスクリプトを呼ぶようにすると、エスケープを気にせずに済みます。
行の途中のコメントは書けない
# で始まる行はコメントですが、コマンドの後ろに # 説明 と続けることはできません。crontab(5)によると、その部分もコマンドの一部としてシェルに渡されます。説明は前の行に書きます。
出力はメールで送られる
コマンドが標準出力や標準エラー出力に何か書くと、cronはその内容をcrontabの持ち主にメールで送ろうとします。MAILTO で送り先を変えられ、MAILTO="" にすると送りません。
メールを受け取る仕組みがないサーバーでは、出力は気づかれないまま失われがちです。出力はファイルに書き出しておくと、失敗したときに後から確かめられます。
*/10 * * * * /usr/local/bin/sync.sh >> /var/log/sync.log 2>&12>&1 を忘れると、エラーのメッセージだけがメールのほうに行きます。cronieは、sendmailが入っていないときはメールの代わりにsyslogへ出力します(cronieのcron(8))。
シェルスクリプト自体を失敗に気づける作りにしておくのも大切です。set -euo pipefailとはで、エラーで止まるスクリプトの書き方を説明しています。
タイムゾーンと夏時間
cronは、デーモンが動いているマシンのタイムゾーンで時刻を判定します。サーバーのタイムゾーンがUTCなら、0 9 * * * は日本時間の18時です。
crontabごとにタイムゾーンを変えられるかは、実装によって違います。
| 実装 | crontabごとのタイムゾーン |
|---|---|
| cronie | CRON_TZ=Asia/Tokyo のように書くと、そのcrontabの時刻をそのタイムゾーンで判定する(ログの時刻はデーモンのタイムゾーンのまま) |
| Debianのcron | 対応していない。TZ= を書いても、コマンドの中の時刻が変わるだけで、実行のタイミングは変わらない |
Debianのcrontab(5)は、ほかのタイムゾーンで動かしたい場合は、毎時動かしてコマンドの中で時刻を調べる方法を紹介しています。
日本には夏時間がありませんが、夏時間のある地域では注意が必要です。cronieのcrontab(5)は、夏時間の切り替えで存在しない時刻に指定したジョブは実行されず、2回ある時刻のジョブは2回実行されうる、と書いています。一方で、cronieとDebianのcron(8)は、3時間未満の時刻の変化を特別に扱い、飛ばされた時刻のジョブはすぐに実行し、戻った時刻のジョブは2回実行しないとしています。対象は決まった時刻に動くジョブで、毎時や毎分のように頻繁に動くジョブは新しい時刻でそのまま動きます。
タイムゾーンや夏時間そのものの考え方は、タイムゾーンと日付の正しい扱い方で詳しく扱っています。
ログの見方
cronがジョブを起動したかどうかは、ログで確かめます。
| 環境 | 確かめ方 |
|---|---|
| Ubuntu、Debian(systemd) | journalctl -u cron、または grep CRON /var/log/syslog |
| RHEL系(cronie) | /var/log/cron |
Ubuntu 24.04では、ジョブを起動するたびに次のような行が出ます(パスはダミーに置き換えています)。
Nov 02 12:00:01 sample-host CRON[12345]: (sample) CMD (/home/sample/bin/sync.sh)この行が出ていれば、cronはコマンドを起動しています。それでも結果がおかしいときは、原因はコマンドの側(PATH、権限、作業ディレクトリなど)です。行が出ていないときは、crontabの書き方、ファイル名、権限、cronデーモンが動いているか(systemctl status cron)を確かめます。
Debianのcronは、起動オプションの -L でログに残す内容を変えられます。既定はジョブの開始だけで、終了や失敗したジョブ(終了コードが0以外)も残すように設定できます(Debianのcron(8))。
二重起動を防ぐ: flock
5分おきに動くジョブが、たまたま5分以上かかると、前の実行が終わる前に次の実行が始まります。cronは前の実行が終わったかどうかを気にしないので、同じ処理が重なって動きます。バックアップや同期の処理では、データが壊れる原因になります。
Linuxでは、util-linuxの flock を使うのが手軽です。
*/5 * * * * flock -n /tmp/sync.lock /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1-n を付けると、ロックが取れないとき(前の実行がまだ動いているとき)は待たずにすぐ終わります。Ubuntu 24.04(util-linux 2.37.2)で試すと、2つ目の flock -n は終了コード1で終わりました。-E 0 を付けると、このときの終了コードを0にできます。失敗の通知を出したくない場合に使います。-w 秒数 を付けると、指定した秒数だけ待ってからあきらめます。
macOSには flock コマンドがありません。代わりに、標準で入っている lockf が使えます。macOSで試したところ、lockf -t 0 ロックファイル コマンド は、ロックが取れないときに already locked と出して終了コード75で終わりました。
flock(1)によると、ロックはファイルが閉じられると自動で外れます。プロセスが途中で強制終了された場合も、プロセスが終わればファイルは閉じられるので、ロックが残り続けることはありません。プロセスの終わり方とシグナルについては、UNIXシグナルとプロセスのライフサイクルで説明しています。
systemd timerとの違い
いまのLinuxでは、cronの代わりにsystemd timerを使う選択肢もあります。どちらを使うかの判断材料を表にします。
| 項目 | cron | systemd timer |
|---|---|---|
| 設定 | crontabに1行 | .timer と .service の2つのファイル |
| 時刻の書き方 | 0 9 * * 1-5 | OnCalendar=Mon..Fri *-*-* 09:00:00 |
| 電源が切れていた間の実行 | されない(Debian系では、anacronを入れると /etc/cron.daily などの日次以上の処理をanacronが受け持つ) | Persistent=true で、起動後にまとめて1回実行する |
| 実行時刻のばらつき | なし(cronieは RANDOM_DELAY で遅らせられる) | RandomizedDelaySec= で、指定した範囲で遅らせる |
| 時刻の精度 | 分単位 | AccuracySec=(既定は1分)。秒単位も書ける |
| ログ | syslogとメール | journalにまとまる(journalctl -u サービス名) |
| 一覧の確認 | crontab -l | systemctl list-timers |
| 二重起動 | 防がない(flockなどを使う) | 同じサービスが動いている間は、新しく起動しない |
| 起動からの経過時間での実行 | @reboot だけ | OnBootSec= などで指定できる |
OnCalendar= の書き方が合っているかは、systemd-analyze calendar で確かめられます。Ubuntu 24.04では次のように表示されました。
$ systemd-analyze calendar "Mon..Fri *-*-* 09:00:00"
Normalized form: Mon..Fri *-*-* 09:00:00
Next elapse: Tue 2026-11-03 09:00:00 JST
(in UTC): Tue 2026-11-03 00:00:00 UTC
From now: 20h left1行で済む手軽さではcron、電源が切れていた間の分の実行やログの管理まで含めるとsystemd timer、という分け方になります。個人のサーバーで「毎晩バックアップ」程度ならcronで十分です。ノートPCのように電源が切れることがあるマシンや、失敗の記録をきちんと残したい処理には、systemd timerが向いています。
macOSではlaunchdが推奨されている
macOSにもcronはありますが、macOSのcrontab(5)には「cronの機能はlaunchdに吸収された」と書かれていて、launchdを使うよう案内しています。launchd.plist(5)によると、時刻を指定したジョブ(StartCalendarInterval)は、スリープ中に時刻を過ぎても、復帰したときに実行されます(何回分か過ぎていても1回にまとめられます)。cronはスリープ中のジョブを飛ばすので、ノートPCではlaunchdのほうが向いています。
launchdの使い方は、macOS launchctlの基本で説明しています。
GitHub Actionsのschedule
GitHub Actionsでも、ワークフローの on.schedule にcronの書き方で時刻を指定できます。ただし、サーバーのcronとは違う点がいくつかあります。GitHub Actionsのドキュメントによると、次のとおりです。
- 既定ではUTCで判定する。
timezoneにIANAのタイムゾーン名(Asia/Tokyoなど)を書くと、そのタイムゾーンで判定する - 一番短い間隔は5分
- 毎時0分のように混み合う時刻は、実行が遅れたり、実行されなかったりすることがある
- 既定のブランチにあるワークフローだけが動く
- 公開リポジトリでは、60日間リポジトリに動きがないと、スケジュール実行が自動で無効になる
@dailyなどの短縮表記は使えない
on:
schedule:
- cron: '0 9 * * 1-5'
timezone: 'Asia/Tokyo'timezone を書かない場合は、日本時間の9時は 0 0 * * 1-5(UTCの0時)になります。混み合う時刻を避けるなら、17 0 * * 1-5 のように中途半端な分にずらすとよいでしょう。
書く前と動かないときのチェックリスト
- 5つのフィールドの順番は「分 時 日 月 曜日」。時と分を逆に書いていないか
- 日と曜日の両方に値を書いていないか(書くとOR)
/etc/crontabと/etc/cron.dにはユーザー名の欄がある。ユーザーのcrontabにはない/etc/cron.dのファイル名にドットが入っていないか(Debian系)- コマンドは絶対パスか、
PATH=を書いたか %を\%にしたか- 出力を
>> ログファイル 2>&1で残しているか - サーバーのタイムゾーンはどこか(
timedatectlなどで確認) - 時間のかかる処理は
flock -nで二重起動を防いでいるか - ログ(
journalctl -u cronなど)に起動の記録が出ているか
まとめ
- crontabは「分 時 日 月 曜日 コマンド」の1行で書く。
*、,、-、/と、月と曜日の名前が使える - 刻みの
/はフィールドの中だけで数える。*/35は35分おきではなく、0分と35分 - 日と曜日の両方に値を書くと「どちらか」に一致した日に動く。Debianのcronは、どちらかの最初の文字が
*ならANDになる - ユーザーのcrontab(
crontab -e)とシステムのcrontab(/etc/crontab、/etc/cron.d)は、ユーザー名の欄の有無が違う - cronの
PATHは/usr/bin:/binだけ。%は改行になる。出力はメールに行くので、ファイルに残す - 二重起動はLinuxなら
flock -n、macOSならlockf -t 0で防ぐ - 電源が切れていた間の実行やログの管理まで必要なら、systemd timerも検討する


