Ubuntu服务器上的定时任务cron一旦执行失败或者报错,默认情况下系统只会把错误信息通过邮件发送到本地用户邮箱,但大多数运维人员根本不会去检查本地邮件,结果就是故障悄无声息地发生了,等发现时业务已经受影响。解决这个问题的核心思路就两步:第一,把cron的标准输出和错误输出重定向到日志文件,方便事后排查;第二,配合一个简单的Shell脚本检测日志中的错误关键词,一旦发现异常就自动发邮件通知到你的邮箱。下面我把整套方案从配置到落地全部讲透。

一、为什么cron任务失败你收不到通知

cron守护进程在Ubuntu上默认的行为是这样的:如果一个定时任务执行过程中产生了任何输出(包括报错信息),cron会尝试把这些内容通过本地邮件系统发送给执行该任务的用户。但问题在于,绝大多数Ubuntu服务器根本没有配置完整的邮件发送服务(比如postfix或sendmail),即便配置了,你也不会天天去看本地邮箱。更要命的是,如果任务执行成功但没有输出,cron什么都不会发,你完全不知道它跑没跑。所以必须主动做日志记录和主动通知,而不是依赖cron的默认行为。

二、cron日志的基本配置方法

Ubuntu系统本身会记录cron的执行情况,日志文件位于/var/log/syslog,你可以用grep命令过滤:

grep CRON /var/log/syslog

这能看到哪些任务在什么时间被触发了,但看不到任务具体的输出内容。要记录每个任务的详细输出,需要在crontab条目中手动加重定向。编辑当前用户的定时任务:

crontab -e

然后把每一行任务改成带日志输出的格式,比如原来是:

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

改成:

0 2 * * * /usr/local/bin/backup.sh >> /var/log/cron_backup.log 2>&1

这里>>表示追加写入,2>&1表示把标准错误也重定向到同一个文件。如果你希望每次执行都清空日志重新记录,就把>>换成>。建议按任务分类建不同的日志文件,比如/var/log/cron_backup.log、/var/log/cron_sync.log等,方便后续管理。

三、编写故障检测与邮件通知脚本

光有日志还不够,你不可能24小时盯着日志文件看。需要一个脚本定时扫描日志,发现错误就发邮件。下面是一个实用的Shell脚本,保存为/usr/local/bin/cron_alert.sh:

#!/bin/bash

# 配置区域
LOG_FILE="/var/log/cron_backup.log"
ERROR_KEYWORDS="error|failed|exception|cannot|denied"
RECIPIENT="admin@yourdomain.com"
SUBJECT="[ALERT] Cron任务异常 - $(hostname)"
MAIL_BODY="/tmp/cron_alert_body.txt"

# 检查日志文件是否存在
if [ ! -f "$LOG_FILE" ]; then
    echo "日志文件不存在: $LOG_FILE" > "$MAIL_BODY"
    echo "请检查cron任务是否正常配置。" >> "$MAIL_BODY"
    mail -s "$SUBJECT" "$RECIPIENT" < "$MAIL_BODY"
    exit 1
fi

# 检查日志最后一次修改时间是否超过24小时(任务可能挂了)
LAST_MOD=$(stat -c %Y "$LOG_FILE")
NOW=$(date +%s)
DIFF=$((NOW - LAST_MOD))

if [ $DIFF -gt 86400 ]; then
    echo "警告:日志文件超过24小时未更新,任务可能已停止执行!" > "$MAIL_BODY"
    echo "最后修改时间: $(date -d @$LAST_MOD)" >> "$MAIL_BODY"
    mail -s "$SUBJECT" "$RECIPIENT" < "$MAIL_BODY"
    exit 0
fi

# 扫描最近一次执行的输出中是否包含错误关键词
LATEST_OUTPUT=$(tail -50 "$LOG_FILE" | grep -iE "$ERROR_KEYWORDS")

if [ -n "$LATEST_OUTPUT" ]; then
    echo "发现错误信息:" > "$MAIL_BODY"
    echo "$LATEST_OUTPUT" >> "$MAIL_BODY"
    echo "" >> "$MAIL_BODY"
    echo "完整日志路径: $LOG_FILE" >> "$MAIL_BODY"
    echo "发生时间: $(date)" >> "$MAIL_BODY"
    mail -s "$SUBJECT" "$RECIPIENT" < "$MAIL_BODY"
fi

给脚本执行权限:

chmod +x /usr/local/bin/cron_alert.sh

这个脚本做了三件事:检测日志文件是否存在、检测任务是否超过24小时没跑、检测最近输出中是否包含错误关键词。你可以根据实际情况修改ERROR_KEYWORDS变量,加入更多业务相关的错误词。

四、配置邮件发送能力

Ubuntu上最简单的邮件发送方式是安装mailutils和配置postfix。先安装:

sudo apt update
sudo apt install mailutils postfix

安装postfix时选择"Internet Site"模式,系统域名填你服务器的域名。如果你有企业邮箱的SMTP账号,也可以用msmtp替代postfix,配置更轻量。msmtp的配置文件~/.msmtprc示例:

account default
host smtp.yourdomain.com
port 587
from admin@yourdomain.com
auth login
user admin@yourdomain.com
password your_password_here
tls on
tls_starttls on

然后把脚本里的mail命令换成:

msmtp "$RECIPIENT" < "$MAIL_BODY"

这样邮件就能通过你的企业邮箱SMTP发出去,不容易被当成垃圾邮件。

五、把检测脚本加入cron定时执行

检测脚本本身也需要定时运行,建议每15分钟或30分钟跑一次。编辑crontab:

crontab -e

添加:

*/15 * * * * /usr/local/bin/cron_alert.sh

这样每15分钟系统就会自动扫描一次日志,有问题立刻发邮件。注意检测脚本的执行频率不要太高,否则会增加系统负担,一般15到30分钟足够了。

六、多任务场景下的统一管理方案

如果你的服务器上有几十个cron任务,每个都单独写检测逻辑太麻烦。可以建一个统一的监控脚本,遍历/var/log/目录下所有cron_*.log文件,批量检测。核心逻辑是用for循环加grep:

#!/bin/bash

LOG_DIR="/var/log"
ERROR_KEYWORDS="error|failed|exception"
RECIPIENT="admin@yourdomain.com"
ALERT_SENT=0

for logfile in $LOG_DIR/cron_*.log; do
    if [ -f "$logfile" ]; then
        if tail -30 "$logfile" | grep -qiE "$ERROR_KEYWORDS"; then
            echo "错误发现于: $logfile" >> /tmp/cron_errors.txt
            ALERT_SENT=1
        fi
    fi
done

if [ $ALERT_SENT -eq 1 ]; then
    mail -s "[ALERT] 多个Cron任务异常" "$RECIPIENT" < /tmp/cron_errors.txt
fi

这种方式扩展性好,新增任务只需要把日志输出重定向到对应的cron_xxx.log文件就行,监控脚本不用改。

七、日志轮转防止磁盘撑爆

日志文件如果长期不清理,会越来越大占满磁盘。必须配置logrotate。创建配置文件/etc/logrotate.d/cron_tasks:

/var/log/cron_*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    create 0640 root adm
}

这表示每天轮转一次,保留30天,压缩旧日志,文件不存在也不报错。这样日志管理就完整闭环了。

八、实战中的常见坑和注意事项

第一,cron任务的环境变量和你手动登录终端不一样,PATH很可能不包含你的程序路径,所以脚本里要用绝对路径,或者在crontab顶部加PATH声明。第二,不要把所有任务的输出都写到同一个日志文件,否则排查时很难定位是哪个任务出了问题。第三,邮件通知不要太频繁,否则你会被大量告警邮件淹没,建议只对真正的错误发通知,任务正常完成不需要通知。第四,如果你用的是systemd timer替代cron,日志会在journalctl里,监控方式要相应调整,用journalctl -u your-service.service来抓取输出。

九、进阶:结合监控平台做可视化

如果你已经在用Zabbix、Prometheus或者轻量级的Uptime Kuma这类监控工具,可以把cron日志的错误计数暴露为指标,做可视化告警。比如用一个简单的exporter脚本把日志中的error数量写到一个文本文件,监控工具定时读取这个文件,超过阈值就触发告警。这比纯邮件通知更直观,适合团队协作场景。

总结一下整套流程:cron任务加输出重定向写日志、logrotate做日志轮转、Shell脚本定时扫描日志中的错误、配置邮件发送把告警推送到你的邮箱。这四步做完,Ubuntu服务器上的定时任务就算出了问题,你也能在第一时间收到通知并快速定位,不会再出现"任务挂了三天没人知道"的情况。