☰
Linux crontab 定时任务全攻略:从原理、实战到避坑
2026/10/6 3:21:55 网站建设 项目流程

做运维、写后端、搞自动化的人,早晚都得跟定时任务打交道。比如数据库每天凌晨备份、请求日志每周归档、监控脚本每五分钟检查一次服务状态……这些活儿如果靠人肉去记、去点,用不了多久就会出乱子。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 哪些场景适合用 crontab

crontab 最擅长的事情,是那些"单机、固定频率、不需要实时反馈"的例行任务。我自己的工作流里,这几类任务全部交给它:

  • 系统维护类:日志切割、临时文件清理、磁盘空间检查、证书续期。
  • 数据备份类:每天凌晨对 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 2>&1

这条任务的意思是每 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_profile,PATH 通常只有/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 2>&1

第三种,命令和脚本一律写绝对路径。比如/usr/bin/python3 /root/scripts/run.py,而不是python3 run.py。

我自己的习惯是脚本内 export PATH 加上所有外部命令写全路径,双保险。别嫌麻烦,任务不执行的时候排查起来更麻烦。

4. 任务不执行?按照这套方法排查

4.1 排查五板斧

定时任务不生效,原因五花八门,但排查顺序基本固定。我总结为五板斧,从外层到里层一层层往内查:

第一板斧:确认 crond 服务还活着。

systemctl status crond

如果你用的是 Ubuntu/Debian,服务名可能叫cron:

systemctl status cron

如果状态不是 active,说明任务根本没被调度,先把服务拉起来。

第二板斧:确认任务确实写进去了。

crontab -l

看看配置是不是保存在当前用户下。如果你用 root 配置了任务,又用普通用户执行crontab -l,自然看不到;反过来也一样,root 的任务只在 root 的crontab -l里出现。

第三板斧:手动执行一次脚本,排除脚本本身的问题。

/bin/bash /root/scripts/myjob.sh

这一步能筛掉大量低级错误。比如脚本没有执行权限、内部命令路径不对、依赖的目录不存在,都会在手动执行时暴露出来。

第四板斧:查看系统 crond 日志。

grep CRON /var/log/cron

CentOS/RHEL 上日志默认在/var/log/cron,Ubuntu/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 2>&1
脚本手动执行正常,cron 下报 command not foundPATH 环境变量不完整脚本开头 export PATH
执行时间比预期晚了 8 小时服务器时区是 UTCtimedatectl set-timezone Asia/Shanghai
日志里出现%相关报错crontab 配置里的 % 没转义改成\%
任务每次启动多个实例上个周期还没跑完又开始使用 flock 文件锁
中文内容乱码cron 环境缺少 LANG 变量脚本开头 export LANG=en_US.UTF-8
明明设置了月初跑,结果每周末也跑日和周两个字段是或关系不要混用,或用脚本内判断日期

4.3 让任务"看得见":日志和告警

定时任务最大的风险不是报错,而是悄悄失败没人知道。crond 默认把任务输出通过邮件发给你,但绝大多数服务器没配邮件服务,输出就丢了。所以我的铁律是:每个任务都必须重定向日志。

0 2 * * * /bin/bash /root/scripts/myjob.sh >> /var/log/myjob.log 2>&1

这个写法里,标准输出和错误输出都会追加到同一个日志文件,方便事后排查。如果你希望失败时能及时收到通知,可以在脚本里加一道判断:

if ! /bin/bash /root/scripts/myjob.sh; then echo "myjob failed at $(date)" | mail -s "task alert" you@example.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 2>&1

-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 LANG=en_US.UTF-8 BACKUP_DIR="/data/backup/mysql" mkdir -p "$BACKUP_DIR" # 1. 备份,注意脚本内部 % 不需要转义 mysqldump -uroot -p'YourPassword' --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 'NR==2 {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 并验证

接着编辑当前的 crontab:

crontab -e

添加这一行:

0 2 * * * /bin/bash /root/scripts/backup_and_clean.sh >> /var/log/backup_and_clean.log 2>&1

保存退出后,先确认任务在列表里:

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 2>&1

等确认 crond 每分钟都在执行,再把时间改回凌晨 2 点。这个"临时改为每分钟确认一下"的思路特别管用,能快速把"配置错误"和"脚本错误"分开。

6. crontab 之外的定时方案什么时候更合适

6.1 systemd timer 的差异和优势

crontab 用着顺手,但它有个明显的年代感。现代 Linux 发行版都集成了 systemd,它自带的 timer 也能做定时任务,而且在某些场景更优雅。

systemd timer 有两个特点值得注意:第一是支持更丰富的日历表达式,比如:

[Timer] OnCalendar=*-*-* 02:00:00 Persistent=true

Persistent=true表示如果机器在预定时间点处于关机状态,下次开机后会补跑错过的任务,这个能力 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 加锁。这几条看着不起眼,每一条背后都有血的教训。希望这篇东西能帮你少踩几个坑,把定时任务真正管得明明白白。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询