RHEL 中級 #6 ジョブスケジューリング — cron、systemd timer、at

読了 10分

運用していると時間ベースで回さなければならない作業が果てしなく出てきます。毎日早朝のバックアップ、毎週レポートメール、毎分ヘルスチェック、1 時間後に 1 度だけ回すスクリプト。RHEL 9 にはこうした作業を扱うツールが cron、anacron、at、systemd timer に分かれています。この記事ではそれぞれをいつ使うかが要点です。

RHEL 中級 シリーズでこの記事の位置:

4 つのツールを 1 行で #

ツール用途マシンが切れていた時間備考
cron反復作業 (毎分/毎時/毎日/…)そのまま飛ばす最も伝統、最も単純
anacronマシンが切れて再度オンになっても保証された日/週/月単位の実行逃した作業を補うノート PC・在宅サーバー必須
at単発予約 (1 時間後に 1 度実行)マシンがオンになった後即時実行1 回限り
systemd timer反復 + 依存性 + ログ統合Persistent=true で補うモダン標準

新しい作業を掴むときのフロー:

  • 反復 + 単純 → cron
  • 反復 + マシンがオンか不確実 → anacron または systemd timer (Persistent=true)
  • 反復 + 依存性 / journald 統合が必要 → systemd timer
  • きっちり 1 度 → at

cron — 伝統の出発点 #

cronie パッケージが RHEL 9 にデフォルトインストールされており、crond サービスが常に実行されます。

確認
$ systemctl status crond
● crond.service - Command Scheduler
     Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled; ...)
     Active: active (running)

cron が見るファイルは 2 系統:

cron が読むところ
/etc/crontab               ← システム全域 (慣習)
/etc/cron.d/<name>         ← システム全域 (drop-in、推奨)
/etc/cron.{hourly,daily,weekly,monthly}/  ← ディレクトリにスクリプトを置くだけで自動実行
/var/spool/cron/<user>     ← ユーザー別 crontab (直接編集 ✗、crontab -e で)

crontab 文法 #

5 つの欄の時刻 + コマンド。最初は目が回りますがよく使うと手に馴染みます。

* * * * * コマンド
│ │ │ │ │
│ │ │ │ └─ 曜日 (0-7、0 と 7 は日曜日)
│ │ │ └─── 月 (1-12)
│ │ └───── 日 (1-31)
│ └─────── 時 (0-23)
└───────── 分 (0-59)

よく使うパターン:

crontab 例
# 毎分
* * * * * /usr/local/bin/healthcheck.sh

# 毎日早朝 3 時 15 分
15 3 * * * /usr/local/bin/backup.sh

# 平日 (月〜金) 午前 9 時
0 9 * * 1-5 /usr/local/bin/morning-report.sh

# 5 分間隔
*/5 * * * * /usr/local/bin/ping.sh

# 9〜18 時、30 分間隔
0,30 9-18 * * * /usr/local/bin/business-hours.sh

# 毎月 1 日 0 時
0 0 1 * * /usr/local/bin/monthly-cleanup.sh

# 略式表現
@reboot   /usr/local/bin/on-startup.sh
@hourly   /usr/local/bin/each-hour.sh
@daily    /usr/local/bin/each-day.sh
@weekly   /usr/local/bin/each-week.sh
@monthly  /usr/local/bin/each-month.sh
@yearly   /usr/local/bin/each-year.sh

ユーザー crontab #

各ユーザーが自分の作業を登録できます。

ユーザー crontab
# 登録/修正 (エディタで開く)
$ crontab -e

# 見る
$ crontab -l

# 空にする
$ crontab -r

# 別のユーザーのもの (root のみ)
$ sudo crontab -u alice -l

crontab -e で編集する理由: /var/spool/cron/<user> を直接 vim で開くと cron が変更を検知できません。crontab -e は変更後 cron にシグナルを送ります。

システム crontab vs ユーザー crontab #

/etc/crontab/etc/cron.d/*ユーザーのカラムが 1 つ多い です。

/etc/cron.d/myjob
# 分 時 日 月 曜日  ユーザー  コマンド
*/10 * * * *      root      /usr/local/bin/sync.sh

運用推奨:

  • ユーザー作業 → crontab -e
  • パッケージ/システム作業 → /etc/cron.d/<name> (drop-in ファイル)
  • /etc/crontab 直接修正は避ける (慣習上システム専用、パッケージが上書きする可能性)

cron 環境変数 #

cron が回すときシェル環境は ログインシェルではありません。 $PATH も異なり $HOME も制限的です。手動で実行したときうまくいったスクリプトが cron で失敗する最もよくある理由。

安全なパターン
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=admin@example.com

15 3 * * * root /usr/local/bin/backup.sh

MAILTO= が空でなければ cron が作業の stdout/stderr をメールで送ります。メール送信が遮断されている環境では、スクリプト内で直接ログファイルにリダイレクトするのが標準です。

ログ redirect
15 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

権限制御 #

/etc/cron.allow/etc/cron.deny でユーザー別 cron 使用を制御します。

  • cron.allow があれば → そこに書かれたユーザーのみ使用可能
  • cron.allow がなく cron.deny があれば → そこに書かれて いない ユーザーのみ使用可能
  • 両方ない場合 (RHEL 9 デフォルト) → root のみ使用可能

運用では cron.allow を使うホワイトリスト方式が一般的です。

anacron — マシンが切れて再度オンになっても保証 #

cron の弱点: マシンが早朝 3 時に切れていたら早朝 3 時のバックアップは そのまま飛ばされます。 翌日まで待たなければなりません。ノート PC、在宅サーバー、たまにオンにするワークステーションで致命的。

anacron は「最後にこの作業が実行されたのはいつか」をディスクに記録します。マシンがオンになるたびに anacron が作動して、最後の実行から指定期間が過ぎていれば即座に実行します。

/etc/anacrontab
SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
RANDOM_DELAY=45
START_HOURS_RANGE=3-22

# 周期  遅延(分)  ジョブ ID     コマンド
1       5         cron.daily     nice run-parts /etc/cron.daily
7       25        cron.weekly    nice run-parts /etc/cron.weekly
@monthly 45       cron.monthly   nice run-parts /etc/cron.monthly

読み方:

  • 1 5 cron.daily ... — 毎日 (period 1)、マシンがオンになった後 5 分後、/etc/cron.daily/* のすべてのスクリプト実行
  • 7 25 ... — 毎週 (period 7)
  • RANDOM_DELAY=45 — 負荷分散用ランダム遅延 0〜45 分

RHEL 9 の動作: anacron は cronie パッケージに一緒に入って systemd timer (anacron.timer) で実行されます。なので /etc/cron.daily/* にスクリプトを置くだけで cron + anacron が一緒に作動してマシンが切れていても次回起動後に補充実行されます。

サーバークラスのマシンはどのみち 24/7 オンで anacron に大きな意味がないですが、知らずに 2 度実行される事故を防ぐには cron.daily 作業の冪等性 (idempotency) を常に気にしなければなりません。

at — 1 度だけ予約 #

「30 分後にこのコマンドを 1 度だけ」のような単発予約は at の領域。

at 基本使用
# パッケージ (RHEL 9 デフォルトインストール ✗ → 直接インストール)
$ sudo dnf install -y at
$ sudo systemctl enable --now atd

# 30 分後
$ at now + 30 minutes
at> /usr/local/bin/restart-app.sh
at> <Ctrl-D>

# 特定時刻
$ at 23:30
$ at 23:30 today
$ at 9am tomorrow
$ at 11:00 2026-05-01

# キューを見る
$ atq

# キュー内容を見る (作業番号で)
$ at -c 3

# 取り消し
$ atrm 3

at も cron のように権限制御ファイルがあります: /etc/at.allow/etc/at.deny

at は batch という変形も提供します。システム負荷が低いとき (load average < 1.5) に実行されるモード。

batch — 負荷が低いとき実行
$ batch
at> /usr/local/bin/heavy-job.sh
at> <Ctrl-D>

夜間バックアップのような重い作業をシステムが暇なときだけ回したいときに有用です。

systemd timer — モダン標準 #

cron が 1975 年に作られたツールなら、systemd timer は 2010 年代のモダンな代替です。RHEL 9 のパッケージたちが徐々に cron から timer に移っています。logrotate、dnf-makecache、fstrim、すべて timer で実行されます。

timer が cron より良い点 #

  • journald 統合 — すべての出力が journald に自動記録 (cron はメールまたは直接 redirect)
  • 依存性After=network-online.target のような systemd 依存性をそのまま使用
  • リソース制御MemoryMax=CPUQuota= など service unit の制御オプションそのまま
  • Persistent=true — マシンが切れていた時間を anacron のように補充
  • OnCalendar= — cron より表現力が広く人が読みやすい
  • trigger 検証systemd-analyze calendar で次の実行時刻をあらかじめ確認

timer + service ペア #

systemd timer は常に 2 つの unit がペアを成します。.service (実際にやること) + .timer (いつ実行するか)。

/etc/systemd/system/backup.service
[Unit]
Description=Daily backup
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=backup
Group=backup
Nice=10
/etc/systemd/system/backup.timer
[Unit]
Description=Run daily backup at 03:15
Requires=backup.service

[Timer]
OnCalendar=*-*-* 03:15:00
RandomizedDelaySec=15min
Persistent=true
Unit=backup.service

[Install]
WantedBy=timers.target

アクティベーションは timer のみ enable します (service は timer がトリガー)。

アクティベーション
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now backup.timer
$ systemctl list-timers --all

OnCalendar 文法 #

cron の * * * * * と比較すると systemd の方がはるかに明確です。

OnCalendar 例
# 毎分
*-*-* *:*:00

# 毎日 03:15
*-*-* 03:15:00
03:15            # 略式

# 平日 09:00
Mon..Fri *-*-* 09:00:00
Mon..Fri 09:00   # 略式

# 5 分間隔
*-*-* *:00/5:00
*:0/5            # 略式

# 毎月 1 日
*-*-01 00:00:00
monthly          # 別名

# 別名たち
hourly / daily / weekly / monthly / yearly

文法検証と次の実行時刻プレビュー:

検証
$ systemd-analyze calendar 'Mon..Fri 09:00'
  Original form: Mon..Fri 09:00
Normalized form: Mon..Fri *-*-* 09:00:00
    Next elapse: Mon 2026-04-27 09:00:00 KST
       From now: 4 days left

ルールを適用する前に意図した時刻か確認する習慣が運用事故を多く防ぎます。

timer トリガーの種類 #

OnCalendar 以外にもいくつかのトリガーがあります。

トリガー意味
OnCalendar=カレンダー時刻 (cron のような絶対時刻)
OnBootSec=起動後 N 秒
OnStartupSec=systemd 開始後 N 秒
OnUnitActiveSec=service が最後にアクティベートされた後 N 秒
OnUnitInactiveSec=service が終了した後 N 秒

例えば OnUnitActiveSec=10min は「前回実行後 10 分後」 — cron の */10 * * * * に似ていますが 前回実行時間基準 なので作業が長引けば次の実行が自然に押されます。cron はそのまま 2 度実行します。

Persistent — anacron の役割まで吸収 #

[Timer]
OnCalendar=daily
Persistent=true

Persistent=true は「最後の実行時刻をディスクに記録し、マシンが切れていて時刻を逃したら起動後に補充実行」 の意味。anacron がやっていたことを timer がそのまま吸収します。

ユーザー timer #

ルート権限なしで自分の作業のみ timer で回すことも可能です。

ユーザー timer
$ mkdir -p ~/.config/systemd/user/
# ~/.config/systemd/user/myjob.service、myjob.timer 作成
$ systemctl --user daemon-reload
$ systemctl --user enable --now myjob.timer
$ systemctl --user list-timers

ユーザーセッションが終了した後も作業が回るようにするには loginctl enable-linger <user> で lingering をオンにする必要があります。

よく見るシステム timer #

現在アクティブな timer 一覧
$ systemctl list-timers
NEXT                        LEFT          LAST                        ...  UNIT
Wed 2026-04-23 04:00:00 KST 5h 12min left Tue 2026-04-22 04:00:00 KST  ... dnf-makecache.timer
Wed 2026-04-23 06:30:00 KST 7h 42min left Tue 2026-04-22 06:30:00 KST  ... logrotate.timer
Wed 2026-04-23 ...                                                    ... fstrim.timer
...

dnf メタデータ更新、logrotate、fstrim、anacron — RHEL 9 デフォルトインストールだけでも timer がいくつもすでに回っています。

cron vs systemd timer — どちらを? #

基準cron 有利timer 有利
単純な「毎分/毎日」
シェル 1 行の作業
journald に出力統合
ネットワーク/マウント依存性
リソース制限 (CPU/メモリ)
パッケージ標準ツール✓ (RHEL 9 トレンド)
学習曲線✓ (5 分で十分)

運用推奨:

  • 既存作業が cron で回っていて単純ならそのまま置く
  • 新規追加するシステム作業は timer で 作成
  • journald 統合と依存性制御が必要なら timer 早期導入

デバッグ — 作業が回らないとき #

cron 作業が回らないとき #

cron デバッグチェックリスト
# 1. crond が生きているか
$ systemctl status crond

# 2. 作業が登録されているか
$ crontab -l                # ユーザー
$ sudo cat /etc/cron.d/*    # システム

# 3. cron 自体のログ
$ sudo journalctl -u crond --since "1 hour ago"

# 4. 作業の stdout/stderr をどこへ送っているか確認
# (MAILTO 空ならメール来ない、redirect なしなら出力消える)

# 5. PATH 問題 — 手動はうまくいくのに cron だけ失敗するなら 99% これ
# crontab 内で PATH= 明示するかコマンドに絶対パス使用

systemd timer 作業が回らないとき #

timer デバッグチェックリスト
# 1. timer が active か
$ systemctl status backup.timer
$ systemctl list-timers backup.timer

# 2. 次回実行予定が意図した時刻か
$ systemd-analyze calendar 'OnCalendar 表現式'

# 3. service が実際に回ったとき何が起きたか
$ journalctl -u backup.service --since "1 day ago"

# 4. 手動で 1 度 trigger してみる
$ sudo systemctl start backup.service

# 5. timer 自体のログ
$ journalctl -u backup.timer

手動トリガーは 「timer の問題か、service 自体の問題か」 を分ける最速の検証。

よくある落とし穴 #

  • cron + 環境変数: 手動シェルと cron シェルが異なる点を忘れるとデバッグが長くなります。絶対パス + crontab 内の PATH= が最も安全。
  • タイムゾーン: cron はシステムタイムゾーン (/etc/localtime) で動作します。コンテナで UTC に設定されたホストに KST 想定の作業を入れると 9 時間ずれます。
  • DST (サマータイム): KST にはありませんが他の地域のマシンは毎年 2 度問題が起きます。OnCalendar は DST 切り替え処理ルールが明示されていて cron より安全。
  • timer の Persistent=true をオンにすると、マシンを数日切ってからオンにしたときに、たまっていた作業が一度に実行されます。バックアップが同時に複数回回ると困るならロック (flock) または冪等処理。
  • at 作業は atd が死んでいるとそのまま消えます。 systemctl enable --now atd 確認。

覚えておくコマンド #

作業コマンド
ユーザー crontab 編集crontab -e
ユーザー crontab を見るcrontab -l
すべての timer を見るsystemctl list-timers --all
timer の次回実行時刻確認systemd-analyze calendar '<expr>'
at キューを見るatq
at 作業の取り消しatrm <id>
cron ログを見るjournalctl -u crond
timer ペアの service ログjournalctl -u <name>.service
手動トリガーsystemctl start <name>.service

まとめ #

  • cron — 単純な反復作業の伝統ツール。crontab -e でユーザー作業、/etc/cron.d/* でシステム作業。
  • anacron — cron がマシンが切れていて逃した日/週/月作業を起動後に補充。RHEL 9 デフォルト。
  • at — 1 度だけ予約。at now + 30 minutes のような単発。atd サービスが必要。
  • systemd timer.timer + .service ペア。journald 統合、依存性、リソース制御、Persistent=true で anacron 吸収。新しい作業は可能ならここへ。
  • デバッグの要点: cron 環境変数、timer は service 単独トリガーで分離検証。

次 — コンテナ入門 #

ここまでが RHEL 9 で時間ベースの作業を運用するツールでした。次は 1 台のマシン内で隔離された環境を複数立ち上げるコンテナへ移ります。

#7 コンテナ入門 — Podman/Buildah/Skopeo (Docker との違い) では RHEL 9 のコンテナ標準である Podman を扱います。Docker とほぼ同じコマンドを使いながらもデーモンなしで動作する構造、rootless コンテナ、Buildah でイメージをビルドするフロー、Skopeo でレジストリの間を移す作業まで 1 サイクルで整理します。

X