crontab -e 配好了任务,到点却没执行?脚本手动跑正常,放进 cron 就报错?本文总结 10 个生产环境最常见的 crontab 踩坑场景,附带完整排查流程。一、crontab 基础语法回顾
# 编辑当前用户的定时任务crontab -e# 查看当前用户的定时任务crontab -l# 查看系统级定时任务cat /etc/crontabls /etc/cron.d/ls /etc/cron.daily/ # 每天执行ls /etc/cron.hourly/ # 每小时执行ls /etc/cron.weekly/ # 每周执行ls /etc/cron.monthly/ # 每月执行
时间格式
* * * * * command│ │ │ │ ││ │ │ │ └── 星期几 (0-7, 0和7都是周日)│ │ │ └──── 月份 (1-12)│ │ └────── 日期 (1-31)│ └──────── 小时 (0-23)└────────── 分钟 (0-59)
常用示例
# 每分钟执行* * * * * /path/to/script.sh# 每5分钟执行*/5 * * * * /path/to/script.sh# 每小时第0分钟执行0 * * * * /path/to/script.sh# 每天凌晨2点执行0 2 * * * /path/to/script.sh# 每周一凌晨3点执行0 3 * * 1 /path/to/script.sh# 每月1号凌晨4点执行0 4 1 * * /path/to/script.sh# 工作日每2小时执行0 */2 * * 1-5 /path/to/script.sh
二、10 个常见踩坑场景
坑1:环境变量缺失(最常见!)
现象:脚本手动执行正常,crontab 不执行或报错。
原因:cron 启动的 shell 是非登录、非交互式的,不会加载 ~/.bashrc、/etc/profile 等环境变量。
排查:
# 在 crontab 里先输出环境变量对比* * * * * env > /tmp/cron_env.txt 2>&1# 对比手动执行的环境变量env > /tmp/manual_env.txtdiff /tmp/cron_env.txt /tmp/manual_env.txt
# 方法1:脚本开头加载环境变量#!/bin/bashsource /etc/profilesource ~/.bashrcexport PATH=/usr/local/bin:/usr/bin:/bin:$PATH# 方法2:在 crontab 顶部定义SHELL=/bin/bashPATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/binHOME=/root* * * * * /path/to/script.sh
坑2:路径使用相对路径
现象:脚本里写了 ./config.ini 或 log/app.log,cron 执行时找不到。
原因:cron 的工作目录是用户家目录,不是脚本所在目录。
解决:脚本里全部用绝对路径
#!/bin/bash# ❌ 错误cd ./apppython main.py >> ./log/app.log 2>&1# ✅ 正确cd /data/apppython /data/app/main.py >> /data/app/log/app.log 2>&1
坑3:权限问题
现象:脚本有执行权限,但 cron 日志显示 Permission denied。
原因:
脚本本身没执行权限
脚本依赖的文件/目录没权限
SELinux 阻止
排查:
# 检查脚本权限ls -l /path/to/script.sh# 需要 -rwxr-xr-x# 检查目录权限ls -ld /path/to/ls -ld /path/# 每一级目录都需要 x 权限# 检查 SELinuxgetenforce# Enforcing 时可能阻止
chmod +x /path/to/script.sh# 或chmod 755 /path/to/script.sh# SELinux 临时关闭(测试用)setenforce 0# 永久关闭sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
坑4:输出没重定向,邮件系统出问题
现象:任务执行了,但没看到结果,或者 /var/spool/mail/ 堆积大量邮件。
原因:cron 默认把输出通过邮件发送给用户,如果邮件系统(postfix/sendmail)没配置,输出就丢了。
解决:养成重定向习惯
# 推荐写法:标准输出和错误都重定向* * * * * /path/to/script.sh >> /var/log/myapp/cron.log 2>&1# 丢弃输出(不推荐用于生产)* * * * * /path/to/script.sh > /dev/null 2>&1# 分别记录 stdout 和 stderr* * * * * /path/to/script.sh >> /var/log/myapp/out.log 2>> /var/log/myapp/err.log
坑5:% 字符未转义
现象:用了 date +%Y%m%d,cron 执行报错。
原因:crontab 里 % 是特殊字符,表示换行,需要转义。
解决:
# ❌ 错误0 2 * * * /path/to/backup.sh /data/backup-$(date +%Y%m%d).tar.gz# ✅ 正确:% 前面加 \0 2 * * * /path/to/backup.sh /data/backup-$(date +\%Y\%m\%d).tar.gz# 或把命令写进脚本,脚本里正常用 %
坑6:crond 服务没运行
现象:啥都不执行,没有任何日志。
排查:
# 检查 crond 服务状态systemctl status crond # CentOS/RHELsystemctl status cron # Ubuntu/Debian# 启动服务systemctl start crondsystemctl enable crond# 检查 cron 是否允许当前用户cat /etc/cron.denycat /etc/cron.allow # 如果存在,只有列表里的用户能用
坑7:时区问题
现象:任务执行了,但时间不对。
原因:系统时区和 cron 时区不一致,或脚本里用了 date 命令。
排查:
# 查看系统时区timedatectl# 查看硬件时钟hwclock# cron 日志里的时间grep CRON /var/log/cron
# 脚本开头指定时区#!/bin/bashexport TZ='Asia/Shanghai'# 或在 crontab 顶部定义TZ=Asia/Shanghai* * * * * /path/to/script.sh
坑8:命令不存在或路径不对
现象:报错 command not found。
原因:cron 的 PATH 不包含命令所在目录,如 /usr/local/bin。
排查:
# 在脚本开头输出 which 结果which pythonwhich javawhich docker
# 方法1:使用绝对路径* * * * * /usr/local/bin/python3 /path/to/script.py# 方法2:扩展 PATHPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin* * * * * python3 /path/to/script.py
坑9:任务执行时间过长,重叠运行
现象:脚本执行时间超过间隔,多个实例同时运行,导致数据混乱。
排查:
# 查看是否有多个相同进程ps aux | grep script.sh
#!/bin/bash# 加锁机制,防止重叠运行LOCKFILE="/tmp/myscript.lock"if [ -f "$LOCKFILE" ]; then PID=$(cat "$LOCKFILE") if ps -p "$PID" > /dev/null 2>&1; then echo "Script is already running (PID: $PID)" exit 1 else echo "Removing stale lock file" rm -f "$LOCKFILE" fifi# 创建锁文件echo $$ > "$LOCKFILE"# 业务逻辑# ...# 清理锁文件rm -f "$LOCKFILE"
或使用 flock:
* * * * * flock -n /tmp/myscript.lock -c '/path/to/script.sh'
坑10:特殊字符和引号问题
现象:命令里有 >、&、* 等字符,执行结果不对。
原因:crontab 对特殊字符的解析和 shell 不同。
解决:
# 复杂命令写进脚本,crontab 只调用脚本# ❌ 不推荐* * * * * curl -s http://api.example.com/data | grep "status" > /tmp/result.txt 2>&1# ✅ 推荐* * * * * /path/to/fetch_data.sh >> /var/log/cron.log 2>&1
三、完整排查流程
# 1. 确认 crond 服务运行systemctl status crond# 2. 查看当前用户的 crontabcrontab -l# 3. 查看系统 cron 日志(CentOS/RHEL)tail -f /var/log/cron# 4. 查看用户邮件(如果有输出未重定向)tail -f /var/spool/mail/root# 5. 测试脚本是否能手动执行/path/to/script.sh# 6. 模拟 cron 环境执行env -i HOME=$HOME PATH=/usr/bin:/bin SHELL=/bin/bash /path/to/script.sh# 7. 检查脚本权限和路径ls -l /path/to/script.shfile /path/to/script.sh# 8. 临时添加调试输出* * * * * /path/to/script.sh >> /tmp/cron_debug.log 2>&1
四、crontab 最佳实践
# 1. 脚本开头规范#!/bin/bashset -euo pipefail # 严格模式:出错即退出source /etc/profileexport PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin# 2. 使用绝对路径LOG_DIR="/var/log/myapp"SCRIPT_DIR="/data/app"cd "$SCRIPT_DIR"# 3. 输出重定向exec >> "$LOG_DIR/cron-$(date +%Y%m%d).log" 2>&1# 4. 加锁防止重叠# ...# 5. 业务逻辑# ...# 6. 清理和收尾
五、系统级 crontab vs 用户级 crontab
| | |
|---|
| crontab -e | |
| /etc/crontab | |
| /etc/cron.d/ | |
| /etc/cron.*ly/ | |
系统级格式(多了用户字段):
# /etc/crontabSHELL=/bin/bashPATH=/sbin:/bin:/usr/sbin:/usr/binMAILTO=root# 分钟 小时 日期 月份 星期 用户 命令0 2 * * * root /path/to/backup.sh
六、总结 checklist
□ 环境变量:脚本开头 source /etc/profile,或 crontab 定义 PATH□ 绝对路径:脚本里所有路径都用绝对路径□ 权限检查:脚本 +x,目录有 x 权限,SELinux 不阻止□ 输出重定向:>> log 2>&1,避免邮件堆积□ % 转义:crontab 里的 % 前面加 \□ 服务状态:systemctl status crond 确认运行□ 时区一致:脚本里 export TZ 或 crontab 顶部定义□ 命令路径:用 which 确认命令位置,或用绝对路径□ 防止重叠:flock 或自定义锁文件□ 复杂命令:写进脚本,crontab 只负责调度
一句话总结
crontab 不执行,80% 是环境变量和路径问题,15% 是权限问题,5% 才是语法问题。记住:手动能跑不算数,模拟 cron 环境能跑才算数。