タイトル: cronによるバッチ実行
SEOタイトル: Linux cron バッチ実行入門(書式・MAILTO・ログ・systemd timer・Laravel Scheduler)
| この記事の要点 |
|
cron とは
cron は Linux/Unix の定期実行デーモンです。「毎日 3 時にバックアップ」「5 分毎に死活監視」など、時間トリガで動かしたいバッチを登録します。crontab(cron table)と呼ばれる設定ファイルを crontab -e で編集すると、cron デーモンが自動的に読み直してくれます。
crontab の基本操作
# 編集($EDITOR で開く。初回は vi が多い)
crontab -e
# 内容表示
crontab -l
# 一括削除
crontab -r
# 別ユーザ(root が他人の crontab を編集)
sudo crontab -u www-data -e
書式: 5 フィールド + コマンド
# ┌───── 分 (0-59)
# │ ┌─── 時 (0-23)
# │ │ ┌─ 日 (1-31)
# │ │ │ ┌ 月 (1-12)
# │ │ │ │ ┌ 曜日 (0-7, 0と7が日曜)
# │ │ │ │ │
# * * * * * コマンド
# 毎日 3 時 0 分
0 3 * * * /usr/bin/php /var/www/cleanup.php
# 5 分毎
*/5 * * * * /home/app/healthcheck.sh
# 毎時 0 分・30 分
0,30 * * * * /home/app/sync.sh
# 平日 9-18 時、1 時間毎
0 9-18 * * 1-5 /home/app/business.sh
# 毎月 1 日の 0:00
0 0 1 * * /home/app/monthly_report.sh
# 日曜深夜 1:30
30 1 * * 0 /home/app/weekly_backup.sh
# 特別表記
@reboot /home/app/startup.sh # 起動時 1 回
@daily /home/app/daily.sh # 0 0 * * * と同じ
@hourly /home/app/hourly.sh # 0 * * * * と同じ
cron の落とし穴: 環境変数と PATH
cron はログイン時とは別の最小限の環境で動きます。.bashrc も読まれません:
# ❌ よくあるミス
# シェルで動くのに cron だと「command not found」
* * * * * php /var/www/run.php
# ✅ 修正1: フルパス指定
* * * * * /usr/bin/php /var/www/run.php
# ✅ 修正2: PATH を crontab 冒頭で指定
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
* * * * * php /var/www/run.php
# 環境変数の設定
SHELL=/bin/bash
HOME=/home/app
LANG=ja_JP.UTF-8
MAILTO=""
* * * * * /home/app/run.sh
ログ出力と通知
# 通常: cron は stdout / stderr をユーザにメール送信
# MAILTO="" でメール無効化(推奨)
MAILTO=""
# 標準出力とエラー両方をログへ
0 3 * * * /usr/bin/php /var/www/run.php >> /var/log/myapp.log 2>&1
# 標準出力は捨て、エラーだけログ
0 3 * * * /usr/bin/php /var/www/run.php > /dev/null 2>> /var/log/myapp.err
# 全部捨てる(非推奨)
0 3 * * * /usr/bin/php /var/www/run.php > /dev/null 2>&1
# エラー時だけメール送信したい場合は MAILTO 指定
MAILTO=alerts@example.com
0 3 * * * /usr/bin/php /var/www/run.php > /dev/null
# → 終了コード非 0 or stderr 出力でメール
シェルスクリプトでラップ(推奨)
crontab に直接コマンドを書くより、シェルスクリプトでラップする方がログ・リトライ・通知を管理しやすい:
#!/bin/bash
# /home/app/jobs/daily_report.sh
set -euo pipefail
LOG=/var/log/myapp/daily_report_$(date +%Y%m%d).log
exec >> "$LOG" 2>&1 # 以後の出力は全てログへ
echo "=== START $(date) ==="
cd /var/www/myapp
# リトライ 3 回
for i in 1 2 3; do
if /usr/bin/php artisan reports:daily; then
echo "Success on attempt $i"
break
fi
echo "Failed attempt $i, retrying..."
sleep 30
done
# 失敗時の Slack 通知
if [ $? -ne 0 ]; then
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"Daily report failed"}' \
"$SLACK_WEBHOOK_URL"
fi
echo "=== END $(date) ==="# crontab
MAILTO=""
0 6 * * * /home/app/jobs/daily_report.sh
多重起動防止: flock
cron は前回が動いていても容赦なく次を起動します。重複防止には flock:
# 同時実行を 1 つだけに(既に動いてたらスキップ)
*/5 * * * * /usr/bin/flock -n /tmp/myjob.lock /home/app/sync.sh
# 既に動いてたら待つ
*/5 * * * * /usr/bin/flock /tmp/myjob.lock /home/app/sync.sh
systemd timer との比較
| 項目 | cron | systemd timer |
|---|---|---|
| 歴史 | 1970 年代から | 2010 年代から |
| 書式 | 1 行 | 2 ファイル(.timer / .service) |
| ログ | 自前で >> log | journalctl で自動収集 |
| 依存・順序 | 難しい | After= / Requires= で表現 |
| 遅延起動 | 不可 | RandomizedDelaySec あり |
| 停止後の再実行 | 不可 | Persistent=true で実行漏れリカバリ |
| 学習コスト | 低 | 中 |
Laravel Scheduler との連携
Laravel はアプリ内で全タスクを管理し、cron に登録するのは1 行だけ:
# crontab -e(www-data 等のアプリユーザで)
* * * * * cd /var/www/myapp && php artisan schedule:run >> /dev/null 2>&1// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
// 毎日 3 時
$schedule->command('cleanup:expired')
->dailyAt('03:00')
->withoutOverlapping()
->appendOutputTo(storage_path('logs/cleanup.log'));
// 5 分毎
$schedule->command('queue:work --stop-when-empty')
->everyFiveMinutes()
->onOneServer();
// 平日 9-18 時の 1 時間毎
$schedule->call(fn () => Cache::flush())
->hourly()
->between('9:00', '18:00')
->weekdays();
}
FAQ
Q: 設定したのに動かない
A: ① cron デーモン稼働確認 systemctl status cron(Debian 系)/crond(RHEL 系)、② /var/log/syslog または /var/log/cron、③ コマンドのフルパス・PATH、④ 実行権限 chmod +x。
Q: 数秒単位で動かしたい
A: cron の最小単位は 1 分。* * * * * sleep 30 && /path/to/job のような小細工か、systemd timer の OnUnitActiveSec=10sec、または常駐デーモン化を検討。
Q: タイムゾーンを変えたい
A: 各エントリの先頭で CRON_TZ=Asia/Tokyo(cron 実装による)。OS タイムゾーンを timedatectl set-timezone Asia/Tokyo で変える方が確実。
📸 参考画像
※ 旧バージョンから引き継いだ参考画像です。手順・図解の補助としてご覧ください。
