cronは指定した時間にコマンドを自動実行する。毎分、毎週火曜の3時、月初の深夜0時——なんでもスケジューリングできる。systemd timerは同じことをやりつつ、ログ管理・依存関係・実行漏れのリカバリまでカバーする。この2つがあれば、Linuxでの定期実行はほぼ全部まかなえる。
要するに、cronはcrontabファイルに5フィールドの時刻指定(分 時 日 月 曜日)とコマンドを書くだけ。systemd timerは .timer と .service の2ファイル構成で、カレンダー指定・起動後指定の両方に対応し、journaldでログを一元管理できる。サクッと設定するならcron、本番で監視したいならsystemd timer。
cronの基本:5フィールド構文
cronジョブは crontab ファイルの1行で定義する。書式はこう:
# ┌───────── 分 (0-59)
# │ ┌─────── 時 (0-23)
# │ │ ┌───── 日 (1-31)
# │ │ │ ┌─── 月 (1-12 または jan-dec)
# │ │ │ │ ┌─ 曜日 (0-7, 0と7=日曜, または sun-sat)
# │ │ │ │ │
* * * * * 実行するコマンド
各フィールドで使える記号:
| 記号 | 意味 | 例 |
|---|---|---|
* | すべての値 | * * * * * = 毎分 |
, | リスト | 1,15,30 * * * * = 1分、15分、30分に実行 |
- | 範囲 | 0 9-17 * * * = 9時〜17時の毎時0分 |
/ | 間隔 | */5 * * * * = 5分ごと |
組み合わせも自由にできる。0 */2 * * mon-fri なら「平日、2時間ごとの0分に実行」。
僕がよく使うパターン:
# 毎日 3:30 AM にバックアップ
30 3 * * * /home/omitsu/scripts/backup-db.sh
# 毎週月曜 9:00 AM にレポート生成
0 9 * * 1 /home/omitsu/scripts/weekly-report.sh
# 平日の業務時間中、15分ごとに稼働チェック
*/15 9-18 * * mon-fri /home/omitsu/scripts/check-uptime.sh
# 毎月1日の深夜0時にクリーンアップ
0 0 1 * * /home/omitsu/scripts/monthly-cleanup.sh
# 6時間ごとにデータ同期
0 */6 * * * /home/omitsu/scripts/sync-data.sh
@ショートカット:5フィールドの省略記法
5フィールドを全部書かなくても、@ プレフィックスで簡潔に書ける:
| ショートカット | 等価表現 | いつ実行されるか |
|---|---|---|
@reboot | — | システム起動時に1回 |
@hourly | 0 * * * * | 毎時0分 |
@daily | 0 0 * * * | 毎日深夜0時 |
@weekly | 0 0 * * 0 | 毎週日曜の深夜0時 |
@monthly | 0 0 1 * * | 毎月1日の深夜0時 |
@yearly | 0 0 1 1 * | 毎年1月1日の深夜0時 |
@reboot は実際かなり便利で、systemdのサービスファイルを書くほどでもない監視スクリプトやSSHトンネルの自動起動に使っている。
cronジョブの管理と落とし穴
crontabコマンド
crontab -e # crontabを編集($EDITORが開く)
crontab -l # 現在の設定を表示
crontab -r # crontab全体を削除(注意!)
crontab -u omitsu -l # 他ユーザーのcrontab表示(root権限必要)
システム全体のcrontabは /etc/crontab にあり、時刻フィールドとコマンドの間にユーザー名が入る:
# /etc/crontab — ユーザー名フィールドあり
*/5 * * * * root /usr/local/bin/system-health-check.sh
crontab -e で編集するユーザー個別のcrontabにはユーザー名は不要。
環境変数の罠
cronは最小限の環境で実行される。ターミナルで動くスクリプトがcronで動かないのは大体これが原因だ。crontabの先頭で環境変数を設定できる:
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=omitsu@32blog.com
# MAILTOを空にするとメール送信なし
# MAILTO=""
30 3 * * * /home/omitsu/scripts/backup.sh
特に重要な変数:
SHELL: コマンドを実行するシェル。デフォルトは/bin/sh(bashではない)PATH: 実行ファイルの検索パス。cronのデフォルトPATHは極端に限定されている。絶対パスを使うか、PATHを明示的に設定するMAILTO: stdout/stderrの送信先。空文字列ならメールなしCRON_TZ: このcrontabのタイムゾーンを上書き(対応していないディストロもある)
cronのログを確認する
ディストリビューションによってログの場所が違う:
# Debian/Ubuntu
grep CRON /var/log/syslog
# RHEL/CentOS/Fedora
grep CRON /var/log/cron
# systemdベース(journald)
journalctl -u cron.service --since "1 hour ago"
cronのよくある落とし穴
1. PATHの問題。 ターミナルでは動くのにcronで動かない。cronは .bashrc も .profile も読み込まないので、コマンドは絶対パスで書く。python3 ではなく /usr/bin/python3。
2. パーミッション。 スクリプトに実行権限(chmod +x script.sh)が必要。また、スクリプトが読み書きするファイルにcron実行ユーザーがアクセスできるか確認する。
3. 「日」と「曜日」の同時指定。 日フィールドと曜日フィールドを両方指定すると、どちらかの条件に合えば 実行される(AND条件ではなくOR条件)。0 0 15 * fri は「15日かつ金曜日」ではなく「15日に実行、さらに毎週金曜にも実行」という意味になる。
4. ジョブの重複実行。 ジョブに10分かかるのに5分間隔で設定すると、前のインスタンスが終わる前に次が始まる。flock で排他制御する:
*/5 * * * * flock -n /tmp/my-job.lock /home/omitsu/scripts/slow-job.sh
systemd timer:モダンな代替手段
systemd timerは2つのファイルで構成される:
.timerユニット — いつ実行するか.serviceユニット — 何を実行するか
慣例として同じベース名を使う。backup.timer は backup.service をトリガーする。
初めてのtimer:毎日のバックアップ
まずserviceファイル(実行する処理の定義):
# /etc/systemd/system/backup.service
[Unit]
Description=Daily database backup
[Service]
Type=oneshot
ExecStart=/home/omitsu/scripts/backup-db.sh
User=omitsu
次にtimerファイル(スケジュールの定義):
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 3:30 AM
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
[Install]
WantedBy=timers.target
有効化して開始:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
これだけ。Persistent=true は「3:30 AMにシステムが停止していた場合、起動後すぐに実行する」という意味。cronにはこの機能がない。
モノトニックタイマーとリアルタイムタイマー
systemd timerには2種類ある:
リアルタイムタイマー は OnCalendar= で特定の時刻に実行する(cronと同じ考え方):
[Timer]
OnCalendar=Mon..Fri *-*-* 09:00:00
モノトニックタイマー はイベントからの相対時間で実行する——起動後、アクティベーション後、前回のサービス完了後:
[Timer]
# 起動5分後
OnBootSec=5min
# timerがアクティブになってから1時間後
OnActiveSec=1h
# 前回のサービス完了30分後
OnUnitInactiveSec=30min
モノトニックタイマーは、cronでは実現できない「35分ごと」問題を解決する。OnUnitInactiveSec=35min なら「前回完了から35分後」なので、重複実行もカレンダー計算の面倒もない。
1つのtimerファイルにモノトニックとリアルタイムのトリガーを混在させることもできる。たとえば OnBootSec=5min と OnCalendar=daily を同時に指定すれば「起動5分後に実行、かつ毎日深夜0時にも実行」になる。
OnCalendar構文とスケジュール例
OnCalendar のフォーマットはcronの5フィールドよりも表現力が高い:
曜日 年-月-日 時:分:秒
各パートは省略可能。cronとの対応表:
| cron表現 | OnCalendar表現 | 意味 |
|---|---|---|
0 * * * * | *-*-* *:00:00 または hourly | 毎時0分 |
0 0 * * * | *-*-* 00:00:00 または daily | 毎日深夜0時 |
0 0 * * 0 | weekly または Sun *-*-* 00:00:00 | 毎週日曜 |
0 0 1 * * | monthly または *-*-01 00:00:00 | 毎月1日 |
0 9 * * 1-5 | Mon..Fri *-*-* 09:00:00 | 平日9時 |
*/15 * * * * | *-*-* *:00/15:00 | 15分ごと |
0 0 1 1 * | yearly または *-01-01 00:00:00 | 毎年1月1日 |
もっと複雑な例:
# 平日の9時と18時
OnCalendar=Mon..Fri *-*-* 09,18:00:00
# 四半期ごと(1月、4月、7月、10月の1日)
OnCalendar=*-01,04,07,10-01 00:00:00
# 月末? systemdには「月の最終日」の指定はない。
# 回避策:28-31日に実行して、スクリプト内で月末かチェック。
systemd-analyzeで事前検証
timerをデプロイする前に、式が正しいか確認できる:
$ systemd-analyze calendar "Mon..Fri *-*-* 09:00:00"
Original form: Mon..Fri *-*-* 09:00:00
Normalized form: Mon..Fri *-*-* 09:00:00
Next elapse: Mon 2026-03-23 09:00:00 JST
(in UTC): Mon 2026-03-23 00:00:00 UTC
From now: 14h left
$ systemd-analyze calendar "hourly" --iterations=5
Original form: hourly
Normalized form: *-*-* *:00:00
Next elapse: Mon 2026-03-23 20:00:00 JST
Iter. 2: Mon 2026-03-23 21:00:00 JST
Iter. 3: Mon 2026-03-23 22:00:00 JST
Iter. 4: Mon 2026-03-23 23:00:00 JST
Iter. 5: Tue 2026-03-24 00:00:00 JST
これはsystemd timerの圧倒的な利点の1つ。有効化する前に「いつ発火するか」を正確に確認できる。
ポイント:テスト時は必ず --iterations=5 を付ける。次の5回の発火時刻を見れば、オフバイワンエラーやタイムゾーンの罠を事前に発見できる。
cron vs systemd timer:どっちを使う?
| 機能 | cron | systemd timer |
|---|---|---|
| セットアップの手間 | crontabに1行 | 2ファイル(.timer + .service) |
| ログ | syslog/メール | journald(ユニット単位で絞り込み可) |
| 実行漏れ | 消える | Persistent=true で復旧 |
| 起動後実行 | @reboot のみ | OnBootSec、OnStartupSec |
| 間隔ベース | 不可 | OnUnitInactiveSec |
| 重複防止 | 手動(flock) | Type=oneshot で標準対応 |
| リソース制限 | なし | cgroup完全対応(CPU・メモリ・IO) |
| 依存関係 | なし | After=、Requires=、Wants= |
| ランダムジッタ | RANDOM_DELAY(限定的) | RandomizedDelaySec |
| ユーザーレベル | crontab -e | ~/.config/systemd/user/ |
cronを使うとき:
- 30秒で設定を終わらせたいとき
- systemdがない環境(コンテナ、古いディストロ、macOS)
- 監視不要のちょっとした自動化
systemd timerを使うとき:
- ログを検索可能にしたいとき(
journalctl -u backup.service) - システム停止中にスケジュールが来ても実行したいとき
- リソース制限が必要なとき(暴走したバックアップにRAMを食い尽くされたくない)
- 他のサービス(ネットワーク、DB、マウントポイント)への依存がある場合
- 「前回完了から30分後」のような間隔ベースのスケジューリング
32blogの運用では、軽いチェック(死活監視、証明書期限アラート)にはcron、DB操作やデプロイパイプラインに関わるものにはsystemd timerを使っている。判断基準はシンプルで、「深夜2時にこれをデバッグする可能性があるか?」。あるならsystemd timer。journalctl -u my-service.service はsyslogを掘り返すより圧倒的に楽だから。
本番で使えるパターンとデバッグ
パターン1:重複実行の防止
cronでは flock が必要だけど、systemdなら Type=oneshot が標準で重複を防ぐ。さらに細かく制御するなら:
[Service]
Type=oneshot
# 1時間以上かかったら強制終了
TimeoutStartSec=3600
# 一時的な失敗時の再起動
Restart=on-failure
RestartSec=60
パターン2:失敗通知
cronは失敗時にメールを送る(メールが設定されていれば)。systemdはもっと柔軟にできる:
# /etc/systemd/system/backup.service
[Unit]
Description=Daily database backup
OnFailure=notify-failure@%n.service
汎用の失敗通知サービスを作っておく:
# /etc/systemd/system/notify-failure@.service
[Unit]
Description=Send failure notification for %i
[Service]
Type=oneshot
ExecStart=/home/omitsu/scripts/notify-slack.sh "Unit %i failed on %H"
User=omitsu
これで、どのサービスでも OnFailure=notify-failure@%n.service を追加するだけでSlack通知が飛ぶ。
パターン3:分散システムでのランダム遅延
50台のサーバーが全部3:00 AMにバックアップを実行したら、バックアップサーバーがパンクする。ジッタを追加:
[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=1800
# 3:00〜3:30 AMの間でランダムに実行
パターン4:ユーザーレベルのtimer(root不要)
root権限なしでもtimerは作れる。ユーザーレベルのsystemdを使う:
mkdir -p ~/.config/systemd/user/
# ~/.config/systemd/user/sync-notes.timer
[Unit]
Description=Sync notes every 30 minutes
[Timer]
OnUnitInactiveSec=30min
OnBootSec=5min
[Install]
WantedBy=timers.target
# ~/.config/systemd/user/sync-notes.service
[Unit]
Description=Sync notes with remote
[Service]
Type=oneshot
ExecStart=%h/scripts/sync-notes.sh
systemctl --user daemon-reload
systemctl --user enable --now sync-notes.timer
systemctl --user list-timers
デバッグチェックリスト
スケジュールしたジョブが動かないとき:
cronの場合:
- cronが動いているか確認:
systemctl status cron - crontab確認:
crontab -l - syslog確認:
grep CRON /var/log/syslog | tail -20 - スクリプトを手動実行:
/path/to/script.sh - PATHの確認——cronのPATHは最小限
- スクリプトとアクセス先のパーミッション確認
systemd timerの場合:
- timerステータス確認:
systemctl status backup.timer - 次回実行時刻確認:
systemctl list-timers backup.timer - サービスログ確認:
journalctl -u backup.service --since "1 hour ago" - サービスを手動実行:
systemctl start backup.service - timerファイルの構文チェック:
systemd-analyze verify backup.timer
FAQ
システム上の全スケジュールジョブを一覧するには?
cronは複数箇所を確認する必要がある:crontab -l(現在のユーザー)、sudo crontab -l -u root(root)、ls /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/(システムレベル)。systemd timerは systemctl list-timers --all で全timerの次回実行時刻と前回実行時刻を表示できる。
cronで30秒ごとに実行できる?
cronの最小単位は1分。定番の回避策は2行書く方法:
* * * * * /path/to/script.sh
* * * * * sleep 30 && /path/to/script.sh
systemdなら OnUnitInactiveSec=30s のモノトニックタイマーを使う。ただし、サブ秒のスケジューリングが必要なら、常駐デーモンの方が適切かもしれない。
cronジョブをsystemd timerに移行するには?
2つのファイルを作る:.service ファイルにコマンド(ExecStart= がcronのコマンドに相当)、.timer ファイルに OnCalendar= でcronと同じスケジュール。実行漏れ復旧が必要なら Persistent=true を追加。systemd-analyze calendar でスケジュールが一致しているか確認してから systemctl enable --now your.timer。
システム停止中のcronジョブはどうなる?
スキップされて終わり。cronには「後追い実行」の概念がない。毎日3 AMのバックアップが設定されていて、サーバーが2 AM〜5 AMの間ダウンしていたら、その日のバックアップは実行されない。systemd timerの Persistent=true はこの問題を解決する——システム復帰後すぐに実行してくれる。
Dockerコンテナ内でcronは使える?
技術的にはYes。でも大抵は間違ったアプローチだ。コンテナは単一プロセスで動くように設計されている。代わりに、ホスト側のcronやsystemd timerから docker exec container_name command を実行するか、コンテナオーケストレーションのスケジューリング機能(Kubernetes CronJobs、ECS Scheduled Tasks)を使う。
cronジョブのタイムゾーンを変えるには?
一部のcron実装では crontab の先頭に CRON_TZ=America/New_York と書ける。対応していない場合は、スクリプト内で TZ=America/New_York date のようにラップする。systemd timerの場合、serviceファイルに Environment=TZ=America/New_York を設定するか、timedatectl set-timezone でシステム全体を変更する。
systemd timerのAccuracySecとは?
AccuracySec= はtimerの発火精度を制御する。デフォルトは1分——つまり03:00:00に設定したtimerが03:00:45に発火することもある。これは意図的な設計で、systemdが複数のtimerをまとめて起こすことで電力を節約している。精度が必要なら AccuracySec=1s を設定するが、ほとんどのジョブではデフォルトで十分。
cronジョブの重複実行を防ぐには?
flock を使う:*/5 * * * * flock -n /tmp/my-job.lock /path/to/job.sh。-n フラグはロックが取れなければ即座に終了する(待機しない)。systemdでは Type=oneshot が標準で重複を防ぐ——サービスがアクティブな間は新しいインスタンスを起動しない。
まとめ
cronとsystemd timerは同じ問題を解決する——コマンドをスケジュール実行する——が、それぞれ違うツールだ。
cron は30秒セットアップ。crontabを編集して1行ペーストすれば終わり。1970年代からこれをやっていて、どの環境でも動く。5フィールド構文は覚えておく価値がある。CI/CDの設定、Kubernetes CronJobs、GitHub Actions、クラウドのスケジューラーなど、至るところで出てくるからだ。
systemd timer は本番グレード。ログ管理、実行漏れ復旧、リソース制限、依存関係の制御、間隔ベースのスケジューリング。2ファイル構成は少し重たいけど、systemctl list-timers と journalctl -u my.service のデバッグ体験はsyslogを掘り返すのとは比べものにならない。
まずはcronでサクッと動かす。信頼性・可観測性・単純な定期実行以上のものが必要になったら、systemd timerに移行する。
CLI自動化の他のツールは、タスクランナーならmake、並列実行ならxargs、全体像はCLIツール完全マップを参照。