crontabが実行されない時のチェックリスト
「手元で実行すると動くのに、cron に登録すると動かない」——原因の大半はPATH が最小限・% のエスケープ漏れ・最終行の改行欠落・実行権限のどれかです。 この記事では crond の稼働確認 → ログ → 書式 → 環境変数 → 権限 の順に、 上から潰せば必ず原因に当たるチェックリストとして整理します。
This guide is a systematic checklist for cron jobs that never fire: verify the cron daemon, read the logs (/var/log/cron, journalctl -u cron), then check the minimal PATH, % escaping, the trailing newline, execute permissions, and the extra user field in system crontabs.
TL;DR
- まず
systemctl status cron(RHEL系はcrond)でデーモン稼働を確認 - ログに
CMD (...)行がある→コマンド側(PATH・権限・%)、ない→crontab 側(書式・改行・デーモン) - cron の PATH は通常
/usr/bin:/binのみ。コマンドはフルパスで書く %は crontab では改行扱い。date +\%Y\%m\%dとエスケープする- crontab の最終行は改行で終わらせる。終わっていないとその行は無効になり得る
/etc/crontab・/etc/cron.d/は6番目にユーザー名フィールドが必要(ユーザーcrontabには不要)
Cron Parser
cron式(5フィールド)を日本語で解読し、次回実行予定の日時を一覧表示。「そもそもスケジュール指定が意図通りか」を10秒で検証できます。ブラウザ完結・外部送信なし。
今すぐ試す →1. crond は動いているか・ログに痕跡はあるか / Daemon & logs first
書式を疑う前に、cron デーモンの稼働と「cron がコマンドを起動した記録」を確認します。 ここで切り分けの軸が決まります。
# Debian / Ubuntu
systemctl status cron
grep CRON /var/log/syslog
journalctl -u cron --since today
# RHEL / CentOS / Amazon Linux
systemctl status crond
sudo tail -n 50 /var/log/cron
journalctl -u crond --since today正常にジョブが起動していれば、ログに次のような CMD 行が残ります。
Jun 11 03:00:01 web01 CRON[21437]: (deploy) CMD (/home/deploy/backup.sh)
Jun 11 03:00:01 web01 CRON[21436]: (CRON) info (No MTA installed, discarding output)CMD 行があるのに結果が出ない場合、cron は正常でコマンド側の失敗(PATH・権限・% → 2〜4章)です。2行目のNo MTA installed, discarding output は「エラー出力がメール送信できず捨てられた」 という印で、失敗の手がかりが消えている状態。まず出力をファイルに残しましょう。
0 3 * * * /home/deploy/backup.sh >> /tmp/backup.log 2>&1If the log shows a CMD line, cron itself works and the command is failing. Redirect stdout/stderr to a file to capture the real error instead of letting cron discard it.
2. PATH は最小限 — フルパスで書く / cron's minimal PATH
cron はログインシェルを経由せず、.bashrc も .profile も読みません。 PATH は通常 /usr/bin:/bin だけ。nvm で入れた node や/usr/local/bin の aws は見つからず、リダイレクトしたログには こんなエラーが残ります。
/bin/sh: 1: node: not found
/bin/sh: 1: aws: not found対処は2つ。which node で調べたフルパスを書くか、crontab 冒頭で PATH を明示します。
# NG: 対話シェルでは動くが cron では not found
* * * * * node /home/deploy/job.js
# OK その1: フルパス指定
* * * * * /usr/local/bin/node /home/deploy/job.js
# OK その2: crontab の先頭で環境変数を宣言
PATH=/usr/local/bin:/usr/bin:/bin
SHELL=/bin/bashcron が実際に見ている環境は、1分間だけ次の行を仕込めば丸ごと確認できます。
* * * * * /usr/bin/env > /tmp/cron-env.txt 2>&1cron does not source your shell rc files; PATH is typically just /usr/bin:/bin. Use absolute paths or declare PATH= at the top of the crontab, and dumpenv from a temporary job to see the exact environment.
3. 書式の罠 — %・最終行の改行・6フィールド目 / Syntax traps
% は改行扱い。crontab(5) の仕様で、エスケープされていない最初の% 以降はコマンドの標準入力に渡され、% 自体は改行に 置換されます。date +%Y%m%d は「date +」として実行され、 バックアップが app-.tar.gz のような名前で作られる事故が典型です。
# NG: 実行されるのは「date +」まで。%Y... は標準入力へ
0 3 * * * tar czf /backup/app-$(date +%Y%m%d).tar.gz /var/www
# OK: % をバックスラッシュでエスケープ
0 3 * * * tar czf /backup/app-$(date +\%Y\%m\%d).tar.gz /var/www最終行は改行で終わらせる。Vixie cron 系は各エントリが改行で終わることを 要求します。crontab -e 経由なら通常問題になりませんが、echo '...' | crontab - や構成管理ツールでファイルを流し込むと 末尾改行が欠け、最終行のジョブだけ動かない・crontab が不完全と扱われることがあります。
システム crontab はフィールドが1つ多い。/etc/crontab と /etc/cron.d/ 配下は6番目に実行ユーザー名が必要です。
# /etc/crontab・/etc/cron.d/*: 6番目にユーザー名(root)が必要
0 3 * * * root /usr/local/bin/backup.sh
# crontab -e のユーザーcrontab: ユーザー欄は書かない
0 3 * * * /usr/local/bin/backup.sh逆にユーザー crontab に root を書くと「root」がコマンドとして解釈され、/bin/sh: 1: root: not found で失敗します。スケジュール部分(5フィールド)が 意図通りかは Cron Parser で次回実行日時を見るのが確実です。
Escape % as \% (everything after an unescaped % becomes stdin), end the crontab with a trailing newline, and remember that /etc/crontab and/etc/cron.d/ entries take an extra user field that per-user crontabs must not have.
4. 実行権限・シバン・相対パス / Permissions & paths
- 実行権限:
ls -l backup.shでxが無ければchmod +x backup.sh。無い場合のログはPermission deniedです。 権限の数値表記は chmod Calculator で確認できます。 - シバン: cron のデフォルトシェルは
/bin/sh。bash 専用構文 ([[ ]]や配列)を使うスクリプトは1行目に#!/bin/bashを明記し、 実行権限を付けるか/bin/bash /path/to/script.shと明示します。 - 相対パス: cron のカレントディレクトリはユーザーの HOME。 スクリプト内の
./config.yamlは手動実行時と別の場所を指します。 スクリプト冒頭でcd "$(dirname "$0")"するか絶対パスに統一を。
Check the execute bit, add a #!/bin/bash shebang (cron uses /bin/shby default), and avoid relative paths — cron starts jobs in the user's home directory.
5. デバッグ手順を1つの表に / The checklist
| 順 / Step | 確認 / Check | コマンド / Command | NGなら / If it fails |
|---|---|---|---|
| 1 | crond 稼働 / Daemon up | systemctl status cron(RHEL系: crond) | systemctl enable --now cron |
| 2 | 登録先のユーザー / Right crontab | crontab -l / sudo crontab -l -u deploy | 別ユーザーに登録していないか確認 |
| 3 | ログに CMD 行 / CMD in logs | journalctl -u cron --since today | 無ければ書式・改行・6フィールド目(3章) |
| 4 | スケジュール指定 / Schedule | Cron Parser で次回実行を確認 | フィールドの読み違いを修正 |
| 5 | 出力の捕捉 / Capture output | >> /tmp/job.log 2>&1 を付ける | エラー文言から PATH・権限・% を特定 |
| 6 | cron の環境 / cron env | * * * * * /usr/bin/env > /tmp/cron-env.txt 2>&1 | PATH / SHELL / HOME を crontab で明示 |
Work top-down: daemon → right user's crontab → CMD lines in the log → schedule → captured output → environment dump. Each step eliminates one whole class of failure.
まとめ / Summary
cron トラブルは「cron がコマンドを起動したか」をログで見極めるのが分岐点です。 起動していればコマンド側(フルパス・\%・権限・シバン)、起動していなければ crontab 側(デーモン・書式・最終行の改行・ユーザーフィールド)。本記事の表を上から なぞれば、「なんとなく再起動して様子を見る」よりはるかに速く原因に到達できます。
When a cron job never runs, split the problem in two with the logs. Ifjournalctl -u cron (or /var/log/cron on RHEL) shows aCMD (...) line, cron started your job and the command itself failed: the usual suspects are the minimal PATH (just /usr/bin:/bin, so every binary needs an absolute path), a missing execute bit, a /bin/sh-incompatible script without a shebang, or an unescaped % — crontab treats % as a newline and feeds the rest to stdin, so date +%Y%m%d silently becomes date +. If there is no CMDline, the crontab itself is the problem: the daemon may be stopped, the entry may be in another user's crontab, the file may lack its trailing newline, or you mixed up the formats — system crontabs (/etc/crontab,/etc/cron.d/) need a sixth user field that per-user crontabs reject. Always append >> /tmp/job.log 2>&1 while debugging so cron stops discarding the evidence, and dump env from a one-minute job to see the real environment.