ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux crontab 定时任务全攻略:从原理、实战到避坑

Linux crontab 定时任务全攻略:从原理、实战到避坑 做运维、写后端、搞自动化的人早晚都得跟定时任务打交道。比如数据库每天凌晨备份、请求日志每周归档、监控脚本每五分钟检查一次服务状态……这些活儿如果靠人肉去记、去点用不了多久就会出乱子。Linux 下最经典、最轻量、覆盖最广的定时任务方案就是 crontab。它不需要额外装任何软件系统自带的 crond 守护进程会按你写好的时间规则到点就把命令或脚本拉起来执行简单又可靠。这篇文章我会从 crontab 的基本原理开始讲把配置语法、常用命令、排查思路、实战案例一步步拆开最后用几个自己踩过的坑帮你避雷。无论你是刚摸 Linux 的新人还是写 Shell 脚本时被定时任务坑过的后端开发都能直接照着抄作业。1. crontab 到底在解决什么问题1.1 crond 守护进程和 crontab 是怎么配合的很多人以为 crontab 是一个独立运行的软件其实它分两个部分一个是让你编辑任务的命令行工具crontab另一个是真正负责到点执行任务的守护进程crond。crond 是系统启动时就常驻后台的一个进程它会每分钟醒来一次扫描系统里所有用户的定时任务配置把当前时间匹配上的命令拉起来执行。这些配置放在不同的位置普通用户的任务通常存在/var/spool/cron/下面以用户名命名的文件里root 用户的任务也一样只不过权限更高系统级的任务则放在/etc/crontab、/etc/cron.d/以及/etc/cron.hourly、/etc/cron.daily这些目录里。所以你在终端里执行crontab -e编辑的只是你自己的那份配置。改完之后不需要重启任何服务crond 每次醒来都会重新读取这也是 crontab 用起来特别顺手的原因之一。1.2 哪些场景适合用 crontabcrontab 最擅长的事情是那些单机、固定频率、不需要实时反馈的例行任务。我自己的工作流里这几类任务全部交给它系统维护类日志切割、临时文件清理、磁盘空间检查、证书续期。数据备份类每天凌晨对 MySQL、PostgreSQL 做全量备份定期归档旧数据。数据同步类每隔几分钟从上游接口拉取数据写入本地数据库。定时通知类每天上班前推送一份日报每周发一次待办提醒。这类任务有个共同点逻辑不复杂失败可重试且对执行时间不敏感晚几秒甚至晚几分钟都能接受。crontab 的分钟级精度完全够用。1.3 哪些场景别硬凑crontab 虽然好用但它不是万能药。下面这几类情况硬用 crontab 会很难受需要秒级触发。crontab 的最小粒度是每分钟一次想每 3 秒轮询一次它做不到。多实例部署的应用。同一套服务起了多个副本如果用 crontab 在每台机器上都跑同一个任务就会出现重复执行。比如订单定时关单两个实例同时跑必然出问题。任务需要失败重试、超时控制、可视化监控、动态调整触发时间。crontab 本身不提供这些能力你得自己在脚本里实现。任务要和业务代码共享状态、配置或者需要下发到大量机器统一管理。这类场景更适合 systemd timer或者应用内的定时任务框架比如 Spring Scheduled、Quartz、XXL-Job后面我会专门展开说。2. 配置语法五个时间字段和四个特殊符号2.1 时间字段的取值规则crontab 的配置行格式其实是固定的一条任务由六部分组成前面五个是时间字段最后一个是要执行的命令分 时 日 月 周 命令五个字段的含义和取值范围如下表字段含义取值范围分第几分钟0-59时第几小时0-23日第几天1-31月第几月1-12周周几0-7其中 0 和 7 都代表周日注意日和周两个字段是或的关系。如果你写了0 8 1 * 1意思是每月 1 号的早上 8 点和每周一的早上 8 点都会触发而不是必须同时满足。这个坑我见过不少同事踩过以为写的是每月的第一个周一实际执行频率比预期高得多。2.2 四个特殊符号怎么组合光会写固定数字还不够crontab 的精髓在于四个特殊符号的灵活组合*表示这个字段的每一个合法值。比如时字段写*代表每小时。,列举多个值。1,15表示第 1 和第 15 分钟。-表示连续范围。1-5表示第 1 到第 5 分钟。/表示步长。在*或一段范围后面加/n表示每隔 n 个单位执行一次。举个例子*/5在分字段里表示每 5 分钟0-30/10表示第 0 到第 30 分钟之间每 10 分钟执行一次也就是第 0、10、20、30 分钟执行。2.3 高频时间写法速查我整理了一份自己经常用到的写法可以直接抄目标时间crontab 写法每分钟执行一次* * * * *每 5 分钟执行一次*/5 * * * *每 2 小时执行一次0 */2 * * *每天凌晨 2 点执行0 2 * * *每周一早上 9 点执行0 9 * * 1每月 1 号和 15 号执行0 0 1,15 * *工作日周一到周五晚上 10 点半执行30 22 * * 1-5每年 6 月 1 日执行0 0 1 6 *这些写法足够覆盖 90% 的日常需求。真的想算复杂时间我一般建议先在在线工具上验证一遍再落到服务器上避免拍脑袋写错。2.4 一个特别容易踩的坑% 需要转义crontab 的配置里%不是普通字符它有特殊含义在命令中出现%的地方会被替换成换行符后面部分会被当成标准输入。也就是说如果你在 crontab 里写* * * * * echo $(date %F %T) tick /tmp/tick.log系统执行时%F %T会被拆掉命令直接报错。正确写法是给每个%加反斜杠* * * * * echo $(date \%F \%T) tick /tmp/tick.log这里要分清这个转义只在 crontab 配置文件的层面需要。如果你把命令写进.sh脚本脚本内部的%正常用就行不需要转义。很多人在这个细节上绕晕只要记住配置文件的 % 永远要写 % 就够了。3. 实操配置你的第一个定时任务3.1 crontab -e 和编辑器选择配置当前用户的定时任务核心命令就是crontab -e。第一次执行时系统会问你想用哪个编辑器编辑我习惯选 vim你也可以选 nano新手用 nano 会友好一些。crontab -e进去之后就是普通编辑操作写入一行任务保存退出。比如*/5 * * * * /usr/bin/curl -s http://127.0.0.1/health /tmp/health.log 21这条任务的意思是每 5 分钟访问一次本地健康检查接口把结果追加到日志文件里。保存退出时crontab 会马上帮你做一次语法检查如果有明显的书写错误它会提示并让你确认是否仍然保存。这个校验机制能挡住不少手误。3.2 日常管理命令-l、-r、-u除了-e编辑还有几个命令需要记一下命令作用补充说明crontab -l列出当前用户的所有定时任务这是最常用的查询命令crontab -r删除当前用户的全部定时任务危险操作没有任何二次确认crontab -u username配合 -e/-l/-r 管理指定用户的任务一般需要 root 权限crontab -i配合 -r 使用删除前询问确认推荐养成用-r -i的习惯特别注意crontab -r它会直接清空你的所有任务不带任何确认。我自己就干过一回手滑的事本来想用-l列出任务结果敲成了-r辛辛苦苦写的十几条任务全没了。从那以后我给自己定了个规矩任何时候要动 crontab 之前先执行一次crontab -l crontab.bak把当前配置备份下来。改动后如果发现问题一条crontab crontab.bak就能恢复。3.3 环境变量问题终端能跑cron 跑不了这是 crontab 新手遇到最多的灵异事件脚本在终端里手动执行一切正常但放进 crontab 后要么报 command not found要么结果不对。原因在于 crond 执行任务时环境几乎是干净的不会加载你登录 shell 时的~/.bashrc、~/.bash_profilePATH 通常只有/usr/bin和/bin。如果你脚本里用了某个装在/usr/local/bin下的命令cron 环境下就找不到。解决方案有三种我通常在脚本和 crontab 里同时用第一种脚本开头显式导出 PATH#!/bin/bash export PATH/usr/local/bin:/usr/bin:/bin:$PATH第二种crontab 配置里统一设置一条环境变量放在所有任务的前面PATH/usr/local/bin:/usr/bin:/bin * * * * * my_cmd /tmp/my.log 21第三种命令和脚本一律写绝对路径。比如/usr/bin/python3 /root/scripts/run.py而不是python3 run.py。我自己的习惯是脚本内 export PATH 加上所有外部命令写全路径双保险。别嫌麻烦任务不执行的时候排查起来更麻烦。4. 任务不执行按照这套方法排查4.1 排查五板斧定时任务不生效原因五花八门但排查顺序基本固定。我总结为五板斧从外层到里层一层层往内查第一板斧确认 crond 服务还活着。systemctl status crond如果你用的是 Ubuntu/Debian服务名可能叫cronsystemctl status cron如果状态不是 active说明任务根本没被调度先把服务拉起来。第二板斧确认任务确实写进去了。crontab -l看看配置是不是保存在当前用户下。如果你用 root 配置了任务又用普通用户执行crontab -l自然看不到反过来也一样root 的任务只在 root 的crontab -l里出现。第三板斧手动执行一次脚本排除脚本本身的问题。/bin/bash /root/scripts/myjob.sh这一步能筛掉大量低级错误。比如脚本没有执行权限、内部命令路径不对、依赖的目录不存在都会在手动执行时暴露出来。第四板斧查看系统 crond 日志。grep CRON /var/log/cronCentOS/RHEL 上日志默认在/var/log/cronUbuntu/Debian 通常混在/var/log/syslog里。搜索你那个任务的脚本名或关键词能直接看到 crond 是否执行了这一行、有没有报错grep myjob /var/log/cron第五板斧看时区和当前时间对不对。date timedatectl如果服务器时区不是东八区你写的0 2 * * *对应的其实是 UTC 时间凌晨 2 点也就是北京时间上午 10 点。任务本身执行了但时间和你预想完全不同。4.2 常见问题速查表把这几年在实际场景里遇到的问题整理成一个速查表排查的时候对着查现象大概率原因解决办法任务完全不执行crontab -l 正常crond 没启动systemctl start crond / cron任务执行了但没有任何输出输出被 crond 丢掉了命令尾部加 /var/log/xxx.log 21脚本手动执行正常cron 下报 command not foundPATH 环境变量不完整脚本开头 export PATH执行时间比预期晚了 8 小时服务器时区是 UTCtimedatectl set-timezone Asia/Shanghai日志里出现%相关报错crontab 配置里的 % 没转义改成\%任务每次启动多个实例上个周期还没跑完又开始使用 flock 文件锁中文内容乱码cron 环境缺少 LANG 变量脚本开头 export LANGen_US.UTF-8明明设置了月初跑结果每周末也跑日和周两个字段是或关系不要混用或用脚本内判断日期4.3 让任务看得见日志和告警定时任务最大的风险不是报错而是悄悄失败没人知道。crond 默认把任务输出通过邮件发给你但绝大多数服务器没配邮件服务输出就丢了。所以我的铁律是每个任务都必须重定向日志。0 2 * * * /bin/bash /root/scripts/myjob.sh /var/log/myjob.log 21这个写法里标准输出和错误输出都会追加到同一个日志文件方便事后排查。如果你希望失败时能及时收到通知可以在脚本里加一道判断if ! /bin/bash /root/scripts/myjob.sh; then echo myjob failed at $(date) | mail -s task alert youexample.com fi哪怕不发邮件也可以把失败信息写到独立文件里再用另一个监控任务去扫这个文件告警。总之要让每个任务都留下痕迹别让它在黑盒里运行。4.4 防止任务重叠flock 文件锁有些脚本执行时间不确定比如备份大数据库可能要跑十几分钟甚至更久。如果 crontab 设定的频率比脚本耗时还短就会出现上一个任务没跑完下一个任务又启动的情况。一台机器上同时跑两个 mysqldump磁盘和 I/O 直接被打满。解决办法是用flock加文件锁*/5 * * * * /usr/bin/flock -xn /tmp/myjobs.lock -c /bin/bash /root/scripts/myjob.sh /var/log/myjob.log 21-x表示获取排他锁-n表示拿不到锁就直接失败退出而不是等待。这样同一时刻只有一个脚本在跑后面的请求会自动放弃避免任务堆积。这是我在生产环境里用的最多的防护手段之一。5. 实战案例数据库备份和日志清理一起做5.1 需求分析与脚本编写现在我以一个非常典型的运维需求来演示完整落地过程每天凌晨 2 点备份 MySQL 的 blog 库备份文件保留 7 天同时检查磁盘使用率超过 80% 就清理/tmp下 3 天前的临时文件。先写脚本/root/scripts/backup_and_clean.sh#!/bin/bash # 备份 blog 数据库并清理过期文件 export PATH/usr/local/mysql/bin:/usr/local/bin:/usr/bin:/bin export LANGen_US.UTF-8 BACKUP_DIR/data/backup/mysql mkdir -p $BACKUP_DIR # 1. 备份注意脚本内部 % 不需要转义 mysqldump -uroot -pYourPassword --single-transaction blog $BACKUP_DIR/blog_$(date %Y%m%d_%H%M%S).sql 2/var/log/mysql_backup.err # 2. 删除 7 天前的备份 find $BACKUP_DIR -name *.sql -mtime 7 -delete # 3. 检查磁盘使用率 DISK_USAGE$(df -P / | awk NR2 {print $5} | sed s/%//) if [ $DISK_USAGE -gt 80 ]; then find /tmp -type f -mtime 3 -delete echo $(date %F %T) disk usage ${DISK_USAGE}%, cleaned /tmp else echo $(date %F %T) backup done, disk usage ${DISK_USAGE}% fi给脚本加执行权限chmod x /root/scripts/backup_and_clean.sh有几个细节我说一下--single-transaction是让 InnoDB 备份时不锁表适合在线备份错误输出单独写到mysql_backup.err这样不会污染正常日志磁盘使用率的判断是全套脚本里最容易出问题的部分df -P /最后一行的%符号必须用sed剥掉转换成纯数字才能和 80 比较。5.2 写入 crontab 并验证接着编辑当前的 crontabcrontab -e添加这一行0 2 * * * /bin/bash /root/scripts/backup_and_clean.sh /var/log/backup_and_clean.log 21保存退出后先确认任务在列表里crontab -l我可以负责任地说第一次配置完别急着等第二天凌晨先手动跑一遍脚本验证流程/bin/bash /root/scripts/backup_and_clean.sh如果脚本输出预期内容、备份文件正常生成再把它交给 crontab 就放心了。为了第一次能直接看到 crond 有没有调度还可以临时把时间改成下一分钟比如* * * * * /bin/bash /root/scripts/backup_and_clean.sh /var/log/backup_and_clean.log 21等确认 crond 每分钟都在执行再把时间改回凌晨 2 点。这个临时改为每分钟确认一下的思路特别管用能快速把配置错误和脚本错误分开。6. crontab 之外的定时方案什么时候更合适6.1 systemd timer 的差异和优势crontab 用着顺手但它有个明显的年代感。现代 Linux 发行版都集成了 systemd它自带的 timer 也能做定时任务而且在某些场景更优雅。systemd timer 有两个特点值得注意第一是支持更丰富的日历表达式比如[Timer] OnCalendar*-*-* 02:00:00 PersistenttruePersistenttrue表示如果机器在预定时间点处于关机状态下次开机后会补跑错过的任务这个能力 crontab 原生没有。第二是任务的输出可以直接交给 journald 管理排查日志时用journalctl统一查看比自己在文件里 grep 舒服。缺点是配置起来要写两个 unit 文件一个.timer一个.service相对繁琐。我的建议是如果就是简单跑个脚本继续用 crontab如果任务涉及依赖关系、需要在开机错过后补执行、或者你想用 systemd 的日志体系统一管理就上 timer。6.2 应用内定时任务和分布式调度怎么选crontab 和 systemd timer 都是操作系统层面的定时方案它们对业务代码内部的事情一无所知。如果你的定时任务本身就是业务的一部分比如订单支付超时自动关闭、每天生成用户报表那我更推荐直接在应用进程里做。Java 生态里简单场景用 Spring 的Scheduled就够了比如每个实例各自处理自己的本地数据。但一旦服务多实例部署Scheduled会带来重复执行问题这时候要么引入分布式锁要么直接用 XXL-Job 或 Quartz 这类专门的调度中间件。XXL-Job 这类平台解决的是调度中心 执行器的架构问题调度中心统一管理所有任务执行器分布在各个应用节点上任务可以指定路由策略支持失败重试、超时熔断、动态修改执行时间、可视化查看执行日志。相比之下crontab 在这些能力上是零它就是一个到点触发脚本的裸工具不做面向业务的高级管理。没法说哪个绝对更好只能按需求分层系统例行任务、机器本地任务、跟业务无关的杂活用 crontab上多实例、需要重试和可视化的业务调度用分布式任务平台单体应用内部的简单周期逻辑直接在代码里做。6.3 一个更现代的视角如果已经跑在云上云厂商的容器编排平台和函数计算也都提供了定时触发能力。比如容器里跑定时任务、serverless 的定时触发器这类方案能做到弹性伸缩、按量付费、免运维。但它们的底层思想依然绕不开到了某个时间点把某个任务拉起来执行这一套逻辑。所以你会发现crontab 这个几十年前的老工具到现在还是很多定时调度方案的思想原型。学会它不仅是在死记一个命令而是在理解定时任务最基本的样子以后再接触 systemd timer、XXL-Job、云函数触发器本质上都是从一个更高的抽象层做同样的事情。我在实际中的体会是crontab 最难的不是语法而是使用习惯。以下几点是雷打不动的脚本开头写 export PATH所有路径写绝对路径输出一律重定向到日志文件改动之前先crontab -l crontab.bak备份凡是可能跑超过一个周期的任务必须用 flock 加锁。这几条看着不起眼每一条背后都有血的教训。希望这篇东西能帮你少踩几个坑把定时任务真正管得明明白白。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进