跳到主要内容
DevOps2026-09-235 分钟阅读

Linux crontab 定时任务实战:命令、日志与排查

crontab 是一个程序,不只是一条语法

很多文章把 crontab 讲成"五个字段的表达式",但真上服务器排障时你会发现:表达式只是入门,crontab 是 Linux 上的定时任务服务(cron daemon)的管理入口。它决定你的任务跑不跑、以谁的身份跑、失败了去哪看。

一条完整的 crontab 条目长这样:

30 3 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

前五段是时间规则(分 时 日 月 周),后面是命令本体。写规则这件事交给在线 Cron 表达式生成器点选即可,用解析器反向验证人类可读描述——本篇聚焦表达式之外的那一半:安装、查看、排查。

三个必会命令,和一个危险按钮

crontab -e    # 编辑当前用户的定时任务
crontab -l    # 列出当前用户的定时任务
crontab -r    # 删除当前用户的全部任务(没有确认!)

-r 紧挨着 -e,手滑误触会静默清空全部任务,是运维圈的经典事故。两个防御动作:

  1. 改动前先备份:crontab -l > ~/crontab.bak.$(date +%F)
  2. 想删单条任务时,永远用 crontab -e 进去注释那一行(行首加 #),而不是 -r 重来。

crontab -e 保存后大多数发行版会自动重载,不需要重启服务;但直接编辑 /var/spool/cron/ 下的文件不会触发重载——别绕过 crontab 命令去改文件。

用户 crontab、/etc/crontab 与 /etc/cron.d 的区别

位置格式适用场景
crontab -e(/var/spool/cron/)5 字段,以当前用户身份执行个人/应用账户的任务
/etc/crontab6 字段,多一个"用户"字段系统级任务的传统位置
/etc/cron.d/xxx同 /etc/crontab 的 6 字段软件包和运维脚本的推荐位置,一个文件一组任务

/etc/cron.d/ 下的文件名只能用字母、数字、下划线和连字符——带点号的文件名(如 backup.sh)会被 cron 直接忽略,这是"文件都在就是不跑"的头号原因。文件权限必须是 0644 且属主 root,否则同样被拒。

@reboot 与 @daily:七个快捷别名

@reboot /opt/scripts/init-cache.sh
@daily  /opt/scripts/cleanup.sh

完整别名:@reboot(启动时)、@yearly/@annually、@monthly、@weekly、@daily/@midnight、@hourly。在 /etc/cron.d/ 里同样可用。@reboot 特别适合容器或云主机重启后重建缓存、重挂载、拉起本地服务——比 systemd unit 轻得多。

任务没跑:五步排查法

按顺序走,绝大多数问题在前三步现形:

  1. cron 服务活着吗:systemctl status crond(CentOS)或 systemctl status cron(Debian/Ubuntu)。
  2. 看 cron 自己的日志:grep CRON /var/log/cron(CentOS)或 /var/log/syslog(Ubuntu)。能查到执行记录说明调度正常,问题在脚本;完全没有记录说明调度层就断了。
  3. 手动执行那行命令:把条目里的命令原样复制到 shell 里跑一遍。cron 环境与你交互 shell 的环境不一样(下一步展开),这是"我手动能跑、cron 不跑"的根源。
  4. 检查 PATH:cron 执行时通常只有 /usr/bin:/bin。脚本里 python3、node、docker 若装在 /usr/local/bin,就会静默找不到。解法:脚本里全部用绝对路径,或在 crontab 顶部显式声明 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin。
  5. 看输出重定向:没有重定向时,任务输出发给本地邮件(很多服务器没配 mail,静默丢弃)。排障期务必加 >> /path/log 2>&1,上线后再决定是否保留。

时区坑:cron 用的是系统时区

0 3 * * * 在一台 TZ=UTC 的云服务器上跑的是北京时间上午 11 点,不是凌晨 3 点。三个自查动作:

timedatectl                    # 看服务器当前时区
date                           # 确认当下时间
ls -l /etc/localtime           # 时区链接指向

团队跨时区协作时,写 crontab 前先用时区转换器对齐一次"北京 3:00 = 服务器几点",把换算结果写进任务注释。Docker 容器内默认 UTC,且容器里通常没有 cron 服务——容器内定时任务应考虑宿主机 cron 调 docker exec,或改用 systemd timer / 应用内的调度库。

百分号 %:crontab 里的隐形换行

crontab 把命令体里未转义的 % 当成「换行」:% 之前是命令,之后的内容通过 stdin 喂给该命令。这是「脚本手动能跑、进了 crontab 就怪」的高频原因——尤其是 date +%Y%m%d 这类写法。

# 错误:%Y 被当成换行分隔符
0 2 * * * /opt/backup.sh /backups/db_$(date +%Y%m%d).sql.gz

# 正确:对每个 % 写成 \%
0 2 * * * /opt/backup.sh /backups/db_$(date +\%Y\%m\%d).sql.gz

更稳妥的做法是把日期拼装放进脚本内部,crontab 只调用脚本路径——命令体越短,踩 % 的机会越少。本站生成器只产出五字段时间规则,不会替你转义命令体里的 %。

MAILTO 与静默失败

未重定向时,cron 把 stdout/stderr 交给本地邮件。很多云镜像根本没装 MTA,邮件进黑洞,任务「看起来什么都没发生」。排障期两件事一起做:

MAILTO=""
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

MAILTO="" 关掉邮件;日志落到有轮转的文件。上线后若要用邮件告警,再设 MAILTO=ops@example.com 并确认本机 sendmail/postfix 真能外发。

秒级任务:cron 做不到,别硬凑

标准 cron 最小粒度是分钟。网上流传的「每 30 秒」写法(同一任务写两行,第二行 sleep 30)能用但脆弱——任务本身耗时超过 30 秒就会叠着跑。真正需要秒级时:

  • 循环 + sleep:脚本内 while true; do ...; sleep 30; done,由 systemd 或 supervisor 守护,最直观。
  • systemd timer:OnUnitActiveSec=30s,原生支持秒级,还有日志(journalctl)和依赖管理。

上线前用解析器对表

写完规则后,在表达式解析器里做两件事:读字段展开与可读描述,确认没有「以为每天、其实每月 3 号」;再看「接下来 5 次触发」(浏览器本地时区)。若服务器是 UTC、你人在东八区,把预览结果加减时差,或在服务器上临时加一条:

*/5 * * * * date >> /tmp/cron-smoke.log 2>&1

观察几轮真实触发后删掉探测行。日与周同时写死时,解析器会按 OR 语义列触发点——这是拦住 0 0 1 * 1 误解的最快办法。语法细节见 Cron 表达式详解。

速查表

需求表达式
每天凌晨 3:3030 3 * * *
每周一早 9:000 9 * * 1
每月 1 号 0:000 0 1 * *
每 5 分钟*/5 * * * *
工作日每天 18:000 18 * * 1-5
开机时@reboot

写完规则先解析验证语义,再上服务器;改 crontab 前先 -l 备份;排障从 /var/log/cron 开始。这三句话能避开 crontab 九成的坑。

排查注记

开篇就划界:表达式只是入门,本篇聚焦「表达式之外的那一半」——安装、查看、排查。危险按钮 -r 单独警示,因为它紧挨着 -e、手滑会静默清空全部任务,防御动作具体到「删单条任务永远用 -e 注释那一行」。五步排查法明说绝大多数问题在前三步现形,第 3 步手动执行的设计意图也点破了:cron 环境与交互 shell 不一样,这是「我手动能跑、cron 不跑」的根源;/etc/cron.d 带点文件名被忽略那条,则标成「文件都在就是不跑」的头号原因。