crontab 定时任务避坑指南:从写对第一条规则到日志排查
几乎每台 Linux 服务器上都跑着几条定时任务:每天凌晨备份数据库、每小时清理临时文件、每五分钟检查一次服务是否存活。cron 看起来简单,一行规则就能搞定,但"写了不跑""手动能跑、定时就失败"这类问题几乎每个人都踩过。这篇文章把 crontab 的常用写法和最常见的坑整理成一份清单,方便日常对照。
一、时间字段怎么读
一条 crontab 规则由 5 个时间字段加一条命令组成:
# 分 时 日 月 周 命令
30 2 * * * /opt/scripts/backup.sh
- 分:0-59
- 时:0-23
- 日:1-31
- 月:1-12
- 周:0-7(0 和 7 都表示周日)
几个常用符号:
*:任意值,:列举,如1,15表示 1 号和 15 号-:范围,如9-18表示 9 点到 18 点/:步长,如*/5表示每 5 个单位
常见例子:
*/5 * * * * 每 5 分钟
0 * * * * 每小时整点
0 9 * * 1-5 工作日早上 9 点
0 3 1 * * 每月 1 号凌晨 3 点
一个容易误会的点:日和周同时指定时,是"或"的关系。0 0 13 * 5 表示每月 13 号或每个周五,而不是"13 号恰好是周五"。
二、编辑与查看
crontab -e # 编辑当前用户的定时任务
crontab -l # 查看
crontab -u www -l # 以 root 查看其他用户的任务
除了用户级 crontab,还有系统级的 /etc/crontab 和 /etc/cron.d/ 目录。系统级文件比用户级多一个"用户"字段:
# /etc/cron.d/cleanup
0 4 * * * root /opt/scripts/cleanup.sh
漏写这个字段,任务就不会按预期执行,这是从 crontab -e 迁移到 /etc/cron.d/ 时最常见的错误。
三、坑一:环境变量和命令行不一样
cron 执行任务时只带一个非常精简的环境,PATH 通常只有 /usr/bin:/bin。你在终端里能直接运行的 node、python3、docker,到了 cron 里可能就是"command not found"。
解决办法有三种,按推荐程度排序:
- 脚本里使用命令的绝对路径,用
which node查到后写死; - 在 crontab 顶部声明
PATH:
PATH=/usr/local/bin:/usr/bin:/bin
SHELL=/bin/bash
- 在脚本开头显式加载需要的环境,例如
source /etc/profile。
同理,工作目录也不是你以为的项目目录,而是用户的家目录。脚本里用到相对路径时,先 cd 到正确位置:
#!/bin/bash
cd /opt/app || exit 1
./run.sh
四、坑二:百分号要转义
在 crontab 的命令部分,% 有特殊含义(会被当作换行)。下面这条规则看起来没问题,实际会执行失败:
0 2 * * * tar czf /backup/db-$(date +%F).tar.gz /data
正确写法是把 % 转义成 \%:
0 2 * * * tar czf /backup/db-$(date +\%F).tar.gz /data
更省心的做法是把复杂命令都放进脚本,crontab 里只调用脚本。
五、坑三:任务重叠执行
如果一个每 5 分钟执行的任务某次跑了 8 分钟,下一次就会和上一次同时运行,可能导致重复处理数据或者文件锁冲突。用 flock 可以很简单地保证同一时间只有一个实例:
*/5 * * * * flock -n /tmp/sync.lock /opt/scripts/sync.sh
-n 表示拿不到锁就立即退出,不排队等待。
六、把输出记下来
cron 默认会把任务输出通过本地邮件发给用户,大多数服务器没有配置邮件,输出就直接丢了。建议每条任务都显式重定向日志:
30 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
2>&1 把错误输出也写进同一个文件,排查时一目了然。日志文件记得配合 logrotate 定期轮转,避免越写越大。
七、任务没执行时怎么排查
按下面的顺序检查,基本能定位绝大多数问题:
- cron 服务是否在运行:
systemctl status cron(CentOS 系为crond)。 - 规则是否被加载:
crontab -l确认内容,注意文件最后一行要有换行。 - 系统日志里有没有触发记录:
grep CRON /var/log/syslog # Debian/Ubuntu
grep CROND /var/log/cron # CentOS/RHEL
journalctl -u cron --since today
- 脚本是否有执行权限:
chmod +x script.sh,并确认首行 shebang 正确。 - 模拟 cron 环境手动执行:
env -i HOME=$HOME PATH=/usr/bin:/bin /bin/sh -c '/opt/scripts/backup.sh'
如果这样手动执行也失败,说明问题出在环境变量或路径上,而不是 cron 本身。
- 时区是否正确:cron 使用系统时区,用
timedatectl确认。容器里的时区经常是 UTC,比北京时间晚 8 小时。
八、小结
写好一条定时任务,记住几条原则就够了:命令用绝对路径,复杂逻辑放进脚本,% 记得转义,长任务加 flock,输出一定要落日志。出问题时先看系统日志,再用精简环境复现。把这些习惯固定下来,cron 就会成为最省心的运维工具之一。
如果你的任务需要更精细的控制,比如依赖其他服务启动、失败自动重试,也可以考虑用 systemd timer 替代 cron,两者可以按场景搭配使用。
- 点赞
- 收藏
- 关注作者
评论(0)