Cron Jobs and Task Scheduling

Cron ジョブとタスクスケジューリング 完全ガイド

目次

  1. はじめに
  2. cron の基礎
  3. ユーザー crontab の管理
  4. システム crontab
  5. cron のアクセス制御
  6. anacron:非24時間稼働システム向けスケジューラ
  7. at コマンドと batch コマンド
  8. systemd タイマー
  9. cron vs systemd タイマー比較
  10. cron ジョブのログとモニタリング
  11. cron のエラーハンドリング
  12. cron の環境変数
  13. ベストプラクティス
  14. よくある落とし穴と対策
  15. トラブルシューティング
  16. 参考文献

1. はじめに

Linux システム管理において、定期的なタスクの自動実行は最も重要な運用業務の一つである。バックアップ、ログローテーション、システム監視、レポート生成など、日常的に繰り返されるタスクを手動で実行することは非効率であり、人的ミスの原因ともなる。

本ドキュメントでは、Linux におけるタスクスケジューリングの主要な仕組みである cron、anacron、at/batch、および systemd タイマーについて、基礎から実践的な運用ノウハウまでを網羅的に解説する。


2. cron の基礎

2.1 cron とは

cron は UNIX/Linux システムにおいて、指定した時刻・間隔でコマンドやスクリプトを自動的に実行するデーモンプロセスである。名前はギリシャ語の「chronos(時間)」に由来する。

主要な cron 実装として以下がある。

実装特徴代表的なディストリビューション
Vixie cron伝統的な cron 実装Debian/Ubuntu (従来)
cronieRed Hat が開発・保守RHEL, CentOS, Fedora, Rocky Linux
busybox cron軽量な組み込み向けAlpine Linux
systemd-cronsystemd タイマーへの橋渡しArch Linux (オプション)

2.2 cron デーモンの仕組み

cron デーモン (crond) は以下の手順で動作する。

  1. デーモン起動時に全ての crontab を読み込む
  2. 毎分、実行すべきジョブがあるかチェック
  3. 該当するジョブがあれば、子プロセスとして実行
  4. crontab ファイルの変更を検知し、自動的にリロード
# cron デーモンの状態確認
$ systemctl status crond        # RHEL系
$ systemctl status cron         # Debian系

# 出力例
● crond.service - Command Scheduler
     Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2026-04-06 09:00:00 JST; 4 days ago
   Main PID: 1234 (crond)
      Tasks: 1 (limit: 49152)
     Memory: 2.4M
     CGroup: /system.slice/crond.service
             └─1234 /usr/sbin/crond -n

2.3 crontab の構文

crontab の各行は、5つの時間フィールドと実行コマンドで構成される。

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

各フィールドで使用可能な表記法:

表記意味
*全ての値* * * * * (毎分)
,複数の値のリスト1,15,30 * * * * (1分、15分、30分)
-範囲指定1-5 * * * * (1〜5分)
/ステップ値*/10 * * * * (10分ごと)
L月の最終日 (一部実装)0 0 L * * (月末)

具体的な設定例:

# 毎日午前3時に実行
0 3 * * * /usr/local/bin/backup.sh

# 平日の午前9時から午後6時まで30分ごとに実行
*/30 9-18 * * 1-5 /usr/local/bin/health_check.sh

# 毎月1日と15日の午前0時に実行
0 0 1,15 * * /usr/local/bin/report.sh

# 毎週日曜日の午前2時に実行
0 2 * * 0 /usr/local/bin/weekly_maintenance.sh

# 1月から3月の毎日午前6時に実行
0 6 * 1-3 * /usr/local/bin/quarterly_task.sh

# 2時間ごとに実行
0 */2 * * * /usr/local/bin/monitoring.sh

2.4 特殊文字列

cron は頻繁に使用されるスケジュールパターンに対して、特殊文字列(ショートカット)を提供する。

文字列意味等価な表現
@rebootシステム起動時なし(特殊)
@yearly / @annually年に1回(1月1日 0:00)0 0 1 1 *
@monthly月に1回(毎月1日 0:00)0 0 1 * *
@weekly週に1回(日曜日 0:00)0 0 * * 0
@daily / @midnight毎日0:000 0 * * *
@hourly毎時0分0 * * * *
# 使用例
@reboot /usr/local/bin/startup_script.sh
@daily /usr/local/bin/daily_backup.sh
@weekly /usr/local/bin/weekly_report.sh

3. ユーザー crontab の管理

3.1 crontab コマンド

ユーザー crontab は crontab コマンドを使用して管理する。

# 現在のユーザーの crontab を表示
$ crontab -l

# crontab を編集(EDITOR 環境変数で指定されたエディタを使用)
$ crontab -e

# crontab を削除(確認なしで全削除されるため注意)
$ crontab -r

# 削除前に確認プロンプトを表示(-i オプション、cronie のみ)
$ crontab -ri

# 特定ユーザーの crontab を表示(root 権限が必要)
$ sudo crontab -u username -l

# ファイルから crontab をインポート
$ crontab mycrontab.txt

# crontab を標準入力から設定
$ echo "0 3 * * * /usr/local/bin/backup.sh" | crontab -

3.2 crontab の編集と確認

# EDITOR を vim に設定して crontab を編集
$ EDITOR=vim crontab -e

# 現在の crontab をファイルにバックアップ
$ crontab -l > ~/crontab_backup_$(date +%Y%m%d).txt

# バックアップから復元
$ crontab ~/crontab_backup_20260410.txt

# 全ユーザーの crontab を一括確認(root 権限)
$ for user in $(cut -f1 -d: /etc/passwd); do
    echo "=== $user ==="
    crontab -u $user -l 2>/dev/null
  done

3.3 実践的な設定例

# ユーザー crontab の全体例
# --- 環境変数の設定 ---
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=admin@example.com

# --- バックアップジョブ ---
# データベースバックアップ(毎日午前2時)
0 2 * * * /home/user/scripts/db_backup.sh >> /var/log/db_backup.log 2>&1

# ファイルバックアップ(毎日午前3時)
0 3 * * * /home/user/scripts/file_backup.sh >> /var/log/file_backup.log 2>&1

# --- 監視ジョブ ---
# ディスク使用率チェック(毎時)
0 * * * * /home/user/scripts/disk_check.sh

# サービス稼働確認(5分ごと)
*/5 * * * * /home/user/scripts/service_monitor.sh > /dev/null 2>&1

# --- レポートジョブ ---
# 日次レポート(毎日午前7時)
0 7 * * * /home/user/scripts/daily_report.sh

# 週次レポート(月曜午前8時)
0 8 * * 1 /home/user/scripts/weekly_report.sh

4. システム crontab

4.1 /etc/crontab

システム crontab は /etc/crontab に格納され、ユーザー crontab とは異なり、実行ユーザーフィールドが追加される。

$ cat /etc/crontab
# /etc/crontab: system-wide crontab
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# 分 時 日 月 曜日 ユーザー コマンド
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6    * * 7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6    1 * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )

重要な注意点:

  • /etc/crontab は直接エディタで編集する(crontab -e は使用しない)
  • ユーザーフィールド(6番目)が必須
  • このファイルの変更は cron デーモンが自動検知する

4.2 /etc/cron.d/ ディレクトリ

/etc/cron.d/ ディレクトリには、パッケージやアプリケーションが独自のスケジュールを配置できる。

$ ls -la /etc/cron.d/
total 24
drwxr-xr-x  2 root root 4096 Apr  1 00:00 .
drwxr-xr-x 90 root root 4096 Apr 10 09:00 ..
-rw-r--r--  1 root root  201 Jan 15 12:00 sysstat
-rw-r--r--  1 root root  102 Feb  1 00:00 logrotate-custom
-rw-r--r--  1 root root  189 Mar 10 00:00 certbot

# /etc/cron.d/sysstat の例
$ cat /etc/cron.d/sysstat
# Activity reports every 10 minutes
*/10 * * * * root /usr/lib64/sa/sa1 1 1
# Summary report at 23:53
53 23 * * * root /usr/lib64/sa/sa2 -A

# /etc/cron.d/certbot の例
$ cat /etc/cron.d/certbot
# Let's Encrypt certificate renewal
0 */12 * * * root certbot renew --quiet --post-hook "systemctl reload nginx"

ファイルの命名規則:

  • ファイル名にドット(.)やチルダ(~)を含めてはいけない(無視される)
  • ファイルのパーミッションは 644 (root 所有) が推奨
  • 構文は /etc/crontab と同じ(ユーザーフィールドが必要)

4.3 定期実行ディレクトリ

# 各ディレクトリの役割
/etc/cron.hourly/    # 毎時実行されるスクリプト
/etc/cron.daily/     # 毎日実行されるスクリプト
/etc/cron.weekly/    # 毎週実行されるスクリプト
/etc/cron.monthly/   # 毎月実行されるスクリプト

# ディレクトリ内のスクリプト例
$ ls -la /etc/cron.daily/
total 48
drwxr-xr-x  2 root root 4096 Apr  1 00:00 .
drwxr-xr-x 90 root root 4096 Apr 10 09:00 ..
-rwxr-xr-x  1 root root  376 Jan 15 00:00 apport
-rwxr-xr-x  1 root root 1478 Mar 20 00:00 apt-compat
-rwxr-xr-x  1 root root  355 Feb  1 00:00 bsdmainutils
-rwxr-xr-x  1 root root  384 Jan 10 00:00 cracklib-runtime
-rwxr-xr-x  1 root root 1187 Mar  5 00:00 dpkg
-rwxr-xr-x  1 root root  372 Jan 20 00:00 logrotate
-rwxr-xr-x  1 root root 1123 Feb 15 00:00 man-db

スクリプトの要件:

  • 実行権限 (chmod +x) が必要
  • ファイル名にドットを含めてはいけない(script.sh は不可、script とする)
  • run-parts コマンドによって実行される
# run-parts の動作確認(ドライラン)
$ run-parts --test /etc/cron.daily/
/etc/cron.daily/apport
/etc/cron.daily/apt-compat
/etc/cron.daily/logrotate
/etc/cron.daily/man-db

# カスタムスクリプトの配置例
$ sudo cat > /etc/cron.daily/custom-cleanup << 'EOF'
#!/bin/bash
# 30日以上前の一時ファイルを削除
find /tmp -type f -mtime +30 -delete
find /var/tmp -type f -mtime +30 -delete
logger "Custom cleanup completed"
EOF
$ sudo chmod +x /etc/cron.daily/custom-cleanup

5. cron のアクセス制御

5.1 cron.allow と cron.deny

cron の利用を制限するために、2つのファイルが用意されている。

/etc/cron.allow    # このファイルに記載されたユーザーのみ cron を使用可能
/etc/cron.deny     # このファイルに記載されたユーザーは cron を使用不可

5.2 アクセス制御の優先順位

アクセス制御は以下の優先順位で判定される。

1. /etc/cron.allow が存在する場合:
   → ファイルに記載されたユーザーのみ使用可能
   → それ以外のユーザーは全て拒否(root を除く)

2. /etc/cron.allow が存在せず、/etc/cron.deny が存在する場合:
   → ファイルに記載されたユーザーのみ拒否
   → それ以外のユーザーは全て許可

3. どちらのファイルも存在しない場合:
   → ディストリビューションにより異なる
   → RHEL系: root のみ使用可能
   → Debian系: 全ユーザーが使用可能
# cron.allow の設定例(特定ユーザーのみ許可)
$ sudo cat > /etc/cron.allow << 'EOF'
root
admin
deploy
monitoring
EOF

# cron.deny の設定例(特定ユーザーのみ拒否)
$ sudo cat > /etc/cron.deny << 'EOF'
guest
testuser
EOF

# アクセス拒否時のエラーメッセージ
$ crontab -e
You (username) are not allowed to use this program (crontab)
See crontab(1) for more information

6. anacron:非24時間稼働システム向けスケジューラ

6.1 anacron の概要

anacron は、常時稼働していないシステム(デスクトップ PC やノートパソコン)向けのタスクスケジューラである。cron はシステムが停止中のタスクをスキップするが、anacron はシステム起動時に未実行のタスクを検出し、実行する。

cron と anacron の比較:

項目cronanacron
最小実行間隔1分1日
デーモンとして動作はいいいえ(実行後終了)
正確な時刻指定はいいいえ
未実行ジョブの検出なしあり
ユーザー別設定ありなし(root のみ)

6.2 anacrontab の設定

$ cat /etc/anacrontab
# /etc/anacrontab: configuration file for anacron
# See anacron(8) and anacrontab(5) for details.

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

各フィールドの意味:

フィールド説明
期間ジョブの実行間隔(日数)。@monthly は特殊値
遅延ジョブ開始前の遅延時間(分)。RANDOM_DELAY が加算される
ジョブIDジョブの識別子。/var/spool/anacron/ にタイムスタンプファイルとして使用
コマンド実行するコマンド
# anacron のタイムスタンプ確認
$ ls -la /var/spool/anacron/
total 24
drwxr-xr-x 2 root root 4096 Apr 10 03:15 .
drwxr-xr-x 7 root root 4096 Jan  1 00:00 ..
-rw------- 1 root root    9 Apr 10 03:15 cron.daily
-rw------- 1 root root    9 Apr  7 03:25 cron.weekly
-rw------- 1 root root    9 Apr  1 03:45 cron.monthly

$ cat /var/spool/anacron/cron.daily
20260410

6.3 cron と anacron の連携

多くのディストリビューションでは、/etc/crontab から anacron が呼び出される仕組みになっている。

# /etc/crontab の典型的な設定
# anacron がインストールされている場合、run-parts は anacron に委譲される
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )

この設定は以下のように動作する:

  1. anacron がインストールされている場合 → anacron が日次タスクを管理
  2. anacron がない場合 → cron が直接 run-parts を実行

7. at コマンドと batch コマンド

7.1 at コマンド

at コマンドは、一度だけ実行するタスクをスケジュールするために使用する。

# at のインストール
$ sudo dnf install at        # RHEL系
$ sudo apt install at         # Debian系

# atd デーモンの起動
$ sudo systemctl enable --now atd

# 基本的な使用方法
$ at 15:00
at> /usr/local/bin/send_report.sh
at> 
job 1 at Fri Apr 10 15:00:00 2026

# 日時指定のバリエーション
$ at 3:00 PM                  # 午後3時
$ at 15:00 April 15           # 4月15日 15:00
$ at now + 2 hours            # 2時間後
$ at now + 30 minutes         # 30分後
$ at now + 1 day              # 1日後
$ at midnight                 # 真夜中
$ at noon                     # 正午
$ at teatime                  # 午後4時

# スクリプトファイルからジョブを投入
$ at 03:00 -f /home/user/scripts/maintenance.sh

# パイプからジョブを投入
$ echo "/usr/local/bin/cleanup.sh" | at now + 1 hour

7.2 batch コマンド

batch コマンドは、システム負荷が低い時にジョブを実行する。デフォルトではロードアベレージが 1.5 以下の時に実行される。

# batch の使用
$ batch
at> /usr/local/bin/heavy_processing.sh
at> 
job 2 at Fri Apr 10 10:30:00 2026

# ロードアベレージの閾値を変更(atd の起動オプション)
# /etc/sysconfig/atd または /etc/default/atd
OPTS="-l 0.8"    # ロードアベレージ 0.8 以下で実行

7.3 atq と atrm

# ジョブキューの確認
$ atq
1    Fri Apr 10 15:00:00 2026 a user
2    Fri Apr 10 10:30:00 2026 b user
3    Sat Apr 11 03:00:00 2026 a user

# 特定ジョブの内容確認
$ at -c 1
#!/bin/sh
# atrun uid=1000 gid=1000
# ...(環境変数のダンプ)
/usr/local/bin/send_report.sh

# ジョブの削除
$ atrm 2
# または
$ at -d 2

# at のアクセス制御
# /etc/at.allow と /etc/at.deny で cron と同様に制御可能

8. systemd タイマー

8.1 systemd タイマーの概要

systemd タイマーは、cron の代替として systemd が提供するタスクスケジューリング機能である。各タイマーは対応するサービスユニットとペアで動作する。

systemd タイマーの利点:

  • ジャーナル(journald)との統合によるログ管理
  • サービスユニットの全機能を活用可能(依存関係、リソース制限など)
  • カレンダー表記による柔軟なスケジューリング
  • Persistent オプションによる実行漏れの防止
  • systemd-analyze calendar による事前検証

8.2 タイマーユニットの作成

タイマーを作成するには、サービスユニット(.service)とタイマーユニット(.timer)の2つのファイルが必要となる。

# サービスユニット: /etc/systemd/system/backup.service
[Unit]
Description=Daily Database Backup
After=network.target postgresql.service

[Service]
Type=oneshot
User=backup
Group=backup
ExecStart=/usr/local/bin/db_backup.sh
StandardOutput=journal
StandardError=journal

# リソース制限
MemoryLimit=512M
CPUQuota=50%
Nice=10
IOSchedulingClass=idle
# タイマーユニット: /etc/systemd/system/backup.timer
[Unit]
Description=Run Database Backup Daily

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=600
AccuracySec=1min

[Install]
WantedBy=timers.target
# タイマーの有効化と起動
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now backup.timer

# タイマーの状態確認
$ systemctl status backup.timer
● backup.timer - Run Database Backup Daily
     Loaded: loaded (/etc/systemd/system/backup.timer; enabled; vendor preset: disabled)
     Active: active (waiting) since Mon 2026-04-06 09:00:00 JST; 4 days ago
      Until: Mon 2026-04-06 09:00:00 JST; 4 days ago
    Trigger: Sat Apr 11 02:00:00 2026; 15h left
   Triggers: ● backup.service

# 全タイマーの一覧表示
$ systemctl list-timers --all
NEXT                          LEFT          LAST                          PASSED   UNIT                    ACTIVATES
Sat 2026-04-11 02:00:00 JST   15h left      Fri 2026-04-10 02:05:23 JST   8h ago   backup.timer            backup.service
Sat 2026-04-11 00:00:00 JST   13h left      Fri 2026-04-10 00:00:00 JST   10h ago  logrotate.timer         logrotate.service
Sat 2026-04-11 06:30:00 JST   20h left      Fri 2026-04-10 06:30:12 JST   4h ago   fstrim.timer            fstrim.service

8.3 OnCalendar による時刻指定

OnCalendar は非常に柔軟な時刻指定形式を提供する。

# OnCalendar の書式
# DayOfWeek Year-Month-Day Hour:Minute:Second

# 具体例
OnCalendar=*-*-* 02:00:00          # 毎日 02:00
OnCalendar=Mon *-*-* 09:00:00      # 毎週月曜 09:00
OnCalendar=*-*-01 00:00:00         # 毎月1日 00:00
OnCalendar=*-01-01 00:00:00        # 毎年1月1日 00:00
OnCalendar=Mon..Fri *-*-* 09:00:00 # 平日の 09:00
OnCalendar=*-*-* *:00:00           # 毎時 0分
OnCalendar=*-*-* *:*:00            # 毎分(秒を省略可)
OnCalendar=*-*-* 00/6:00:00        # 6時間ごと(0, 6, 12, 18時)
OnCalendar=Sat *-*-1..7 02:00:00   # 毎月第1土曜日 02:00

# 簡略表記
OnCalendar=hourly                   # 毎時
OnCalendar=daily                    # 毎日 00:00
OnCalendar=weekly                   # 毎週月曜 00:00
OnCalendar=monthly                  # 毎月1日 00:00
OnCalendar=yearly                   # 毎年1月1日 00:00

# systemd-analyze calendar で次回実行時刻を確認
$ systemd-analyze calendar "*-*-* 02:00:00"
  Original form: *-*-* 02:00:00
Normalized form: *-*-* 02:00:00
    Next elapse: Sat 2026-04-11 02:00:00 JST
       (in UTC): Fri 2026-04-10 17:00:00 UTC
       From now: 15h 30min left

$ systemd-analyze calendar "Mon *-*-* 09:00:00" --iterations=5
  Original form: Mon *-*-* 09:00:00
Normalized form: Mon *-*-* 09:00:00
    Next elapse: Mon 2026-04-13 09:00:00 JST
       From now: 3 days left
       Iter. #2: Mon 2026-04-20 09:00:00 JST
       Iter. #3: Mon 2026-04-27 09:00:00 JST
       Iter. #4: Mon 2026-05-04 09:00:00 JST
       Iter. #5: Mon 2026-05-11 09:00:00 JST

8.4 OnBootSec / OnUnitActiveSec

単調タイマー(monotonic timer)は、特定のイベントからの経過時間を基準にジョブを実行する。

# /etc/systemd/system/monitoring.timer
[Unit]
Description=System Monitoring Timer

[Timer]
# システム起動から5分後に初回実行
OnBootSec=5min
# 前回のサービス実行完了から10分後に再実行
OnUnitActiveSec=10min
# 精度
AccuracySec=1sec

[Install]
WantedBy=timers.target

利用可能な単調タイマーオプション:

オプション基準イベント
OnBootSecシステム起動
OnStartupSecsystemd 起動(ユーザーセッションの場合)
OnActiveSecタイマーユニット自体のアクティブ化
OnUnitActiveSec対応するサービスの最終アクティブ化
OnUnitInactiveSec対応するサービスの最終非アクティブ化
# 複合的な設定例(起動後 + 定期実行)
[Timer]
OnBootSec=2min
OnUnitActiveSec=15min

8.5 Persistent オプション

Persistent=true を設定すると、システム停止中にスケジュールされていたジョブが次回起動時に実行される(anacron と同様の機能)。

[Timer]
OnCalendar=daily
Persistent=true    # システム停止中の実行漏れを防止
# Persistent の動作確認
$ systemctl show backup.timer | grep -E "LastTrigger|NextElapse"
LastTriggerUSec=Fri 2026-04-10 02:05:23 JST
NextElapseUSecRealtime=Sat 2026-04-11 02:00:00 JST

8.6 一時的タイマー (systemd-run)

systemd-run を使用すると、ユニットファイルを作成せずに一時的なタイマーを設定できる。

# 30分後にコマンドを実行
$ sudo systemd-run --on-active=30min /usr/local/bin/cleanup.sh
Running timer as unit: run-r1234567890.timer
Will run service as unit: run-r1234567890.service

# 特定の時刻に実行
$ sudo systemd-run --on-calendar="2026-04-11 15:00:00" /usr/local/bin/report.sh

# ユーザーセッションで一時タイマー
$ systemd-run --user --on-active=1h /home/user/scripts/task.sh

# 一時タイマーの確認
$ systemctl list-timers --all | grep run-

9. cron vs systemd タイマー比較

機能cronsystemd タイマー
最小実行間隔1分1秒(マイクロ秒精度も可)
カレンダー表記5フィールド形式ISO 8601 ベース
ログ管理syslog / メールjournald 統合
依存関係管理なしユニットの依存関係で制御可能
リソース制限なしcgroups ベースの制限
実行漏れ防止なし(anacron が必要)Persistent=true
ランダム遅延なし(RANDOM_DELAY は anacron のみ)RandomizedDelaySec
設定の複雑さ1行で記述可能2つのユニットファイルが必要
ユーザー管理crontab -e で簡単~/.config/systemd/user/ に配置
状態確認crontab -l のみsystemctl list-timers で詳細表示
イベント駆動時刻のみ起動後、ユニット変化後なども可
テスト実行コマンドを手動実行systemctl start service.service
セキュリティcron.allow/denySystemCallFilter, ProtectSystem 等

推奨される使い分け:

  • cron が適している場合:

    • 単純な定期実行タスク
    • 既存システムとの互換性が重要
    • クイックに設定したい場合
  • systemd タイマーが適している場合:

    • 依存関係やリソース制限が必要
    • 詳細なログ管理が必要
    • Persistent 実行が必要
    • セキュリティの強化が必要

10. cron ジョブのログとモニタリング

10.1 ログの確認方法

# RHEL系: cron ログの確認
$ sudo grep CRON /var/log/cron
Apr 10 02:00:01 server CROND[12345]: (root) CMD (/usr/local/bin/backup.sh)
Apr 10 02:00:01 server CROND[12346]: (user) CMD (/home/user/scripts/monitoring.sh)

# Debian系: syslog から確認
$ sudo grep CRON /var/log/syslog
Apr 10 02:00:01 server CRON[12345]: (root) CMD (/usr/local/bin/backup.sh)

# journald を使用する場合
$ journalctl -u crond --since today
$ journalctl -u cron --since "2026-04-10 02:00:00" --until "2026-04-10 03:00:00"

# systemd タイマーのログ
$ journalctl -u backup.service --since today
Apr 10 02:00:01 server systemd[1]: Starting Daily Database Backup...
Apr 10 02:05:23 server db_backup.sh[12345]: Backup completed successfully
Apr 10 02:05:23 server systemd[1]: backup.service: Deactivated successfully.
Apr 10 02:05:23 server systemd[1]: Finished Daily Database Backup.

10.2 メール通知

cron はジョブの標準出力・標準エラー出力をメールで送信する。

# crontab での MAILTO 設定
MAILTO=admin@example.com
# 複数宛先
MAILTO="admin@example.com,ops@example.com"
# メール送信を無効にする
MAILTO=""

# メール送信を無効にしつつ、ログを残す方法
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# 出力が空の場合はメールを送信しない(cronie の -n オプション)
CRONDARGS="-n"

10.3 モニタリングの実装

#!/bin/bash
# /usr/local/bin/cron_wrapper.sh
# cron ジョブのラッパースクリプト(モニタリング対応)

JOB_NAME="$1"
shift
COMMAND="$@"
LOG_FILE="/var/log/cron_jobs/${JOB_NAME}.log"
LOCK_FILE="/var/run/cron_jobs/${JOB_NAME}.lock"
METRICS_FILE="/var/lib/node_exporter/textfile_collector/${JOB_NAME}.prom"

# ディレクトリの作成
mkdir -p /var/log/cron_jobs /var/run/cron_jobs /var/lib/node_exporter/textfile_collector

# 開始時刻の記録
START_TIME=$(date +%s)
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting: ${JOB_NAME}" >> "${LOG_FILE}"

# コマンド実行
eval "${COMMAND}" >> "${LOG_FILE}" 2>&1
EXIT_CODE=$?

# 終了時刻と実行時間の計算
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))

echo "[$(date '+%Y-%m-%d %H:%M:%S')] Finished: ${JOB_NAME} (exit=${EXIT_CODE}, duration=${DURATION}s)" >> "${LOG_FILE}"

# Prometheus メトリクスの出力
cat > "${METRICS_FILE}" << EOF
# HELP cron_job_exit_code Exit code of the last cron job execution
# TYPE cron_job_exit_code gauge
cron_job_exit_code{job="${JOB_NAME}"} ${EXIT_CODE}
# HELP cron_job_duration_seconds Duration of the last cron job execution
# TYPE cron_job_duration_seconds gauge
cron_job_duration_seconds{job="${JOB_NAME}"} ${DURATION}
# HELP cron_job_last_run_timestamp_seconds Timestamp of the last cron job execution
# TYPE cron_job_last_run_timestamp_seconds gauge
cron_job_last_run_timestamp_seconds{job="${JOB_NAME}"} ${END_TIME}
EOF

# エラー時の通知
if [ ${EXIT_CODE} -ne 0 ]; then
    echo "ALERT: Cron job '${JOB_NAME}' failed with exit code ${EXIT_CODE}" | \
        mail -s "[ALERT] Cron Job Failure: ${JOB_NAME}" admin@example.com
fi

exit ${EXIT_CODE}
# ラッパースクリプトの使用例(crontab 内)
0 2 * * * /usr/local/bin/cron_wrapper.sh db_backup /usr/local/bin/db_backup.sh

11. cron のエラーハンドリング

11.1 終了コードの処理

#!/bin/bash
# /usr/local/bin/backup_with_error_handling.sh

set -euo pipefail

# エラーハンドラ
cleanup() {
    local exit_code=$?
    if [ ${exit_code} -ne 0 ]; then
        echo "[ERROR] Backup failed with exit code ${exit_code}" >&2
        # アラート送信
        curl -s -X POST "https://hooks.slack.com/services/xxx/yyy/zzz" \
            -H 'Content-Type: application/json' \
            -d "{\"text\": \"Backup failed on $(hostname) at $(date)\"}"
    fi
    # 一時ファイルの清掃
    rm -f /tmp/backup_*.tmp
    exit ${exit_code}
}

trap cleanup EXIT

# メイン処理
echo "Starting backup at $(date)"
pg_dump mydb > /tmp/backup_$(date +%Y%m%d).sql
gzip /tmp/backup_$(date +%Y%m%d).sql
aws s3 cp /tmp/backup_$(date +%Y%m%d).sql.gz s3://my-backup-bucket/
echo "Backup completed at $(date)"

11.2 ロック機構の実装

同じジョブが同時に複数実行されることを防ぐためのロック機構。

#!/bin/bash
# flock を使用したロック(推奨)
exec 200>/var/lock/my_cron_job.lock
flock -n 200 || { echo "Job is already running"; exit 1; }

# メイン処理
echo "Processing..."
sleep 60
echo "Done"
# crontab 内で直接 flock を使用する方法
*/5 * * * * flock -n /var/lock/monitoring.lock /usr/local/bin/monitoring.sh

# タイムアウト付き flock
*/5 * * * * flock -w 60 /var/lock/monitoring.lock /usr/local/bin/monitoring.sh
# PIDファイルを使用したロック(レガシー手法)
#!/bin/bash
PIDFILE="/var/run/my_job.pid"

if [ -f "${PIDFILE}" ]; then
    PID=$(cat "${PIDFILE}")
    if kill -0 "${PID}" 2>/dev/null; then
        echo "Process ${PID} is still running"
        exit 1
    else
        echo "Stale PID file found, removing"
        rm -f "${PIDFILE}"
    fi
fi

echo $$ > "${PIDFILE}"
trap "rm -f ${PIDFILE}" EXIT

# メイン処理

11.3 タイムアウト処理

# timeout コマンドの使用
*/30 * * * * timeout 1800 /usr/local/bin/long_running_job.sh

# timeout + flock の組み合わせ
*/30 * * * * flock -n /var/lock/job.lock timeout 1800 /usr/local/bin/job.sh

# systemd タイマーでのタイムアウト設定
# /etc/systemd/system/job.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/job.sh
TimeoutStartSec=1800

12. cron の環境変数

12.1 デフォルト環境変数

cron は非常に限定的な環境でコマンドを実行する。これは多くの問題の原因となる。

# cron のデフォルト環境変数
SHELL=/bin/sh
PATH=/usr/bin:/bin
HOME=/home/username
LOGNAME=username

# 通常のログインシェルとの比較
# ログインシェル
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/user/.local/bin

# cron 環境
$ crontab -l
* * * * * echo "PATH=$PATH" >> /tmp/cron_env.log
$ cat /tmp/cron_env.log
PATH=/usr/bin:/bin

12.2 環境変数の設定方法

# 方法1: crontab 内で直接設定
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
JAVA_HOME=/usr/lib/jvm/java-17
NODE_PATH=/usr/local/lib/node_modules
MAILTO=admin@example.com

0 2 * * * /usr/local/bin/backup.sh

# 方法2: スクリプト内で環境を読み込み
0 2 * * * . /etc/profile; /usr/local/bin/backup.sh

# 方法3: env コマンドを使用
0 2 * * * env PATH=/usr/local/bin:/usr/bin:/bin JAVA_HOME=/usr/lib/jvm/java-17 /usr/local/bin/backup.sh

# 方法4: スクリプト内で明示的に設定
#!/bin/bash
source /etc/profile
source ~/.bashrc
export PATH="/usr/local/sbin:/usr/local/bin:$PATH"
# メイン処理

12.3 PATH の取り扱い

# 推奨: コマンドのフルパスを使用する
# 悪い例
0 2 * * * mysqldump mydb > /tmp/backup.sql

# 良い例
0 2 * * * /usr/bin/mysqldump mydb > /tmp/backup.sql

# コマンドのパス確認
$ which mysqldump
/usr/bin/mysqldump
$ type -a python3
python3 is /usr/bin/python3
python3 is /usr/local/bin/python3

13. ベストプラクティス

1. ログを残す

# 標準出力と標準エラー出力をログに記録
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# ローテーション対応のログ
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup_$(date +\%Y\%m\%d).log 2>&1

2. ロック機構を使う

# 同時実行を防止
*/5 * * * * flock -n /var/lock/job.lock /usr/local/bin/job.sh

3. タイムアウトを設定する

# 長時間実行を防止
0 2 * * * timeout 3600 /usr/local/bin/job.sh

4. コメントを残す

# === データベースバックアップ ===
# 担当: 運用チーム
# 最終更新: 2026-04-10
# 関連ドキュメント: https://wiki.example.com/backup
0 2 * * * /usr/local/bin/db_backup.sh >> /var/log/db_backup.log 2>&1

5. バックアップを取る

# crontab のバックアップスクリプト
#!/bin/bash
BACKUP_DIR="/opt/crontab_backups"
mkdir -p "${BACKUP_DIR}"
DATE=$(date +%Y%m%d_%H%M%S)

# 全ユーザーの crontab をバックアップ
for user in $(cut -f1 -d: /etc/passwd); do
    crontab -u "${user}" -l > "${BACKUP_DIR}/${user}_${DATE}.crontab" 2>/dev/null
done

# システム crontab のバックアップ
cp /etc/crontab "${BACKUP_DIR}/system_${DATE}.crontab"
cp -r /etc/cron.d/ "${BACKUP_DIR}/cron.d_${DATE}/"

# 30日以上前のバックアップを削除
find "${BACKUP_DIR}" -type f -mtime +30 -delete

6. 実行タイミングをずらす

# 悪い例: 全てのジョブが同時刻に集中
0 0 * * * /job1.sh
0 0 * * * /job2.sh
0 0 * * * /job3.sh

# 良い例: 適度にタイミングをずらす
0 0 * * * /job1.sh
10 0 * * * /job2.sh
20 0 * * * /job3.sh

# systemd タイマーの場合は RandomizedDelaySec を活用
[Timer]
OnCalendar=daily
RandomizedDelaySec=1800    # 最大30分のランダム遅延

7. 冪等性を保証する

#!/bin/bash
# 冪等なバックアップスクリプト
BACKUP_FILE="/backup/db_$(date +%Y%m%d).sql.gz"

# 既にバックアップが存在する場合はスキップ
if [ -f "${BACKUP_FILE}" ]; then
    echo "Backup already exists: ${BACKUP_FILE}"
    exit 0
fi

pg_dump mydb | gzip > "${BACKUP_FILE}.tmp"
mv "${BACKUP_FILE}.tmp" "${BACKUP_FILE}"

14. よくある落とし穴と対策

落とし穴 1: PATH が通っていない

# 問題: cron 環境では PATH が制限される
*/5 * * * * python3 /home/user/script.py    # 失敗する可能性

# 対策: フルパスを使用
*/5 * * * * /usr/bin/python3 /home/user/script.py

落とし穴 2: パーセント記号のエスケープ

# 問題: cron では % が改行として解釈される
0 2 * * * echo "Date: $(date +%Y-%m-%d)" >> /tmp/log    # 失敗

# 対策: % をバックスラッシュでエスケープ
0 2 * * * echo "Date: $(date +\%Y-\%m-\%d)" >> /tmp/log

落とし穴 3: crontab -r の誤操作

# 問題: crontab -r は確認なしで全エントリを削除
$ crontab -r    # キーボードで e と r は隣接 → 誤操作リスク

# 対策1: エイリアスで確認プロンプトを追加
alias crontab='crontab -i'

# 対策2: crontab の定期バックアップ
0 * * * * crontab -l > /home/user/.crontab_backup

落とし穴 4: タイムゾーンの問題

# cron はシステムのタイムゾーンを使用する
$ timedatectl
               Local time: Fri 2026-04-10 10:30:00 JST
           Universal time: Fri 2026-04-10 01:30:00 UTC
                 RTC time: Fri 2026-04-10 01:30:00
                Time zone: Asia/Tokyo (JST, +0900)

# cronie では CRON_TZ 変数でタイムゾーンを変更可能
CRON_TZ=America/New_York
0 9 * * * /usr/local/bin/us_report.sh    # ニューヨーク時間の午前9時

# systemd タイマーではタイムゾーンは UTC が基本
# ローカルタイムを明示する場合
OnCalendar=*-*-* 02:00:00 Asia/Tokyo

落とし穴 5: 日と曜日の同時指定

# 注意: 日と曜日を同時に指定すると OR 条件になる
# 以下は「毎月15日 または 毎週金曜日」に実行される
0 0 15 * 5 /usr/local/bin/job.sh

# 「毎月15日の金曜日のみ」にしたい場合はスクリプト内でチェック
0 0 15 * * [ "$(date +\%u)" = "5" ] && /usr/local/bin/job.sh

落とし穴 6: 出力の大量生成

# 問題: 大量の出力がメールキューを圧迫
*/5 * * * * /usr/local/bin/verbose_script.sh    # 大量のメールが送信される

# 対策: 出力をログにリダイレクトし、メール送信を抑制
*/5 * * * * /usr/local/bin/verbose_script.sh >> /var/log/verbose.log 2>&1

15. トラブルシューティング

ジョブが実行されない

# チェックリスト
# 1. cron デーモンが動作しているか
$ systemctl status crond

# 2. crontab が正しく設定されているか
$ crontab -l

# 3. cron ログにエラーがないか
$ sudo grep CRON /var/log/cron
$ sudo journalctl -u crond --since "1 hour ago"

# 4. スクリプトに実行権限があるか
$ ls -la /usr/local/bin/backup.sh
$ chmod +x /usr/local/bin/backup.sh

# 5. スクリプトが正しく動作するか(手動テスト)
$ /usr/local/bin/backup.sh

# 6. 環境変数の問題がないか
$ env -i SHELL=/bin/sh PATH=/usr/bin:/bin /usr/local/bin/backup.sh

# 7. cron.allow / cron.deny に制限がないか
$ cat /etc/cron.allow
$ cat /etc/cron.deny

# 8. SELinux のコンテキストが正しいか
$ ls -Z /usr/local/bin/backup.sh
$ ausearch -m avc --start today

systemd タイマーが動作しない

# チェックリスト
# 1. タイマーが有効か
$ systemctl is-enabled backup.timer
$ systemctl status backup.timer

# 2. 対応するサービスの状態
$ systemctl status backup.service

# 3. サービスのログ確認
$ journalctl -u backup.service -n 50

# 4. タイマーの次回実行時刻
$ systemctl list-timers backup.timer

# 5. ユニットファイルの構文チェック
$ systemd-analyze verify /etc/systemd/system/backup.timer
$ systemd-analyze verify /etc/systemd/system/backup.service

# 6. 手動実行テスト
$ sudo systemctl start backup.service
$ journalctl -u backup.service -f

16. 参考文献

  • man ページ:

    • man 5 crontab - crontab ファイルの書式
    • man 1 crontab - crontab コマンドの使い方
    • man 8 cron - cron デーモンの説明
    • man 8 anacron - anacron の説明
    • man 1 at - at コマンドの使い方
    • man 5 systemd.timer - systemd タイマーユニット
    • man 7 systemd.time - systemd 時刻表記
  • 公式ドキュメント:

  • 関連ツール:

  • 書籍:

    • 「Linuxシステム管理標準教科書」LPI-Japan
    • 「UNIX and Linux System Administration Handbook」Evi Nemeth 他
    • 「Linux Command Line and Shell Scripting Bible」Richard Blum

本ドキュメントは 2026年4月時点の情報に基づいています。最新の情報は各ソフトウェアの公式ドキュメントを参照してください。