状況の説明
crontabエントリを設定して実行を待っても、何も起きません。出力もログもエラーもない。そして/var/log/syslog(RHEL/CentOSの場合は/var/log/cron)を確認すると、こんなメッセージが見つかります:
CRON[12345]: (root) CMD (/opt/scripts/backup.sh)
CRON[12345]: (CRON) info (no MTA installed, discarding output)
実はジョブは実行されています。cronが実行したのです。しかし、スクリプトが生成した出力は、それを配信するメール転送エージェント(MTA)がインストールされていないため、静かに破棄されてしまいました。スクリプトがエラーコード1で終了していたとしても、あなたには一切わかりません。
なぜこうなるのか
cronはジョブの出力をcrontabの所有者にメールで送るよう設計されています。最小構成のUbuntuやCentOSサーバーには、postfix、sendmail、ssmtpなどのMTAが存在しないため、cronはその警告をログに記録して出力を破棄します。
本番環境では、これは深刻な問題です。バックアップスクリプトが毎晩午前2時に失敗し続けていても、その痕跡がまったく残らないことになります。
手っ取り早い解決策:crontabで出力をリダイレクトする
パッケージのインストールは不要です。crontabエントリの中で直接、標準出力と標準エラー出力をリダイレクトするだけです:
# すべての出力をログファイルに追記する
*/5 * * * * /opt/scripts/backup.sh >> /var/log/backup-cron.log 2>&1
# すべての出力を破棄する(スクリプト自身がログを持つ場合のみ使用)
*/5 * * * * /opt/scripts/backup.sh > /dev/null 2>&1
cronが出力をメールする先がなくなれば、MTAの警告は表示されなくなります。
どちらのオプションが適切かを判断するには:
- スクリプトに内部ログがない場合は
>> /var/log/backup-cron.log 2>&1を使用してください。特に午前3時に何か問題が起きたとき、記録が必要になります。 - スクリプトがすでに構造化されたログを書き込んでいる場合、または出力が本当に不要な場合にのみ
> /dev/null 2>&1を使用してください。
修正を確認する
crontabを更新した後、次のスケジュール実行を待ってsyslogを確認します:
# cronのアクティビティをリアルタイムで監視する
tail -f /var/log/syslog | grep CRON
# RHEL/CentOSの場合
tail -f /var/log/cron
正常な実行はこのようになります — CMDの行が表示され、その後にMTA警告が続きません:
CRON[12346]: (root) CMD (/opt/scripts/backup.sh)
次に、ログファイルに書き込まれていることを確認します:
tail -f /var/log/backup-cron.log
恒久的な解決策:crontabの先頭にMAILTOを設定する
5つのcronジョブを管理していますか?各行に2>&1リダイレクトを追加するのはすぐに面倒になります。よりすっきりしたアプローチとして、ファイルの先頭でMAILTOを一度だけ空に設定する方法があります。cronはそのファイル内のすべてのジョブのメール配信をスキップします。
crontab -e
MAILTO=""
*/5 * * * * /opt/scripts/backup.sh >> /var/log/backup-cron.log 2>&1
0 2 * * * /opt/scripts/db-dump.sh >> /var/log/db-dump.log 2>&1
30 6 * * 1 /opt/scripts/weekly-report.sh >> /var/log/weekly.log 2>&1
>> logfile 2>&1のリダイレクトはそのまま残しておいてください — MAILTO=""は警告を黙らせますが、出力を有用な場所に送る仕組みは引き続き必要です。
ジョブが本当に実行されていない場合
no MTA installedのメッセージは、実はジョブが実行されたことを確認する意味で安心材料です。しかし、syslogにCMDの行がまったく存在しない場合は、別の問題があります。以下の点を順番に確認してください:
1. cronデーモンが動作していることを確認する
# Debian/Ubuntu
systemctl status cron
# RHEL/CentOS
systemctl status crond
停止している場合は起動してください:sudo systemctl start cron(またはcrond)。
2. crontabの構文を確認する
crontab -l
crontabを静かに壊してしまう3つのよくあるミス:
- 末尾の改行がない — ファイルの最後のエントリは改行で終わる必要があります。そうでないとcronはそれを無視します
- エスケープされていない
%記号 — cronは%を改行文字として扱います。date +%Y-%m-%dのようなコマンドでは\%とエスケープしてください - フィールドの順番が間違っている — 形式は
分 時 日 月 曜日 コマンドです
cronが正常に動作しているか確認したいですか?毎分実行されるテストジョブを追加してみましょう:
* * * * * echo "cron alive $(date)" >> /tmp/cron-test.log 2>&1
/tmp/cron-test.logに60秒ごとに新しい行が追加されれば、cronは正常です。問題はスクリプト側にあります。
3. スクリプトのパーミッションを確認する
ls -l /opt/scripts/backup.sh
chmod +x /opt/scripts/backup.sh
スクリプトに実行権限が必要です。また、crontabを所有するユーザーがそのファイルを読み取り・実行できることを確認してください — 特にスクリプトが制限されたパーミッションのディレクトリに存在する場合は注意が必要です。
4. cron内のPATHは最小限
cronのデフォルトPATHは通常/usr/bin:/binだけです。シェルで正常に動作するpython3、aws、nodeなどのコマンドが、まったく見つからない場合があります。対処方法は2つあります:
# オプションA:crontabの先頭でPATHを設定する
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
*/5 * * * * /opt/scripts/backup.sh >> /var/log/backup-cron.log 2>&1
# オプションB:スクリプト内で絶対パスを使用する
/usr/bin/python3 /opt/scripts/process.py
5. 環境変数が読み込まれない
cronは.bashrcや.bash_profileを読み込みません。AWS_PROFILE、JAVA_HOME、DATABASE_URLなどの変数は、明示的に設定しない限り存在しません。スクリプトの先頭に以下を追加してください:
#!/bin/bash
source /etc/environment
# または変数を直接設定する
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export AWS_PROFILE=production
クイックリファレンス
- 警告が表示されるがジョブは実行された: crontabエントリに
>> /logfile 2>&1を追加するか、先頭にMAILTO=""を設定する - syslogにCMDの行がない: デーモンの状態、crontabの構文、ファイルのパーミッション、PATHを確認する
- 手動では動くがcron経由では動かない: ほぼ必ずPATHか環境変数の不足が原因

