crontab 命令详解:定时任务格式、参数与配置示例
crontab 是 Linux 定时任务的配置表,每行写“分 时 日 月 周”五个时间字段加一条命令,cron 服务每分钟检查一次、到点执行。crontab -e 编辑,-l 查看,-r 删除全部任务且不会确认;系统级任务写在 /etc/crontab 或 /etc/cron.d/,每行比用户 crontab 多一个用户名字段。
crontab 是 Linux 上配置定时任务的命令,也是这张任务表本身的名字。每个用户有一张自己的 crontab,每行一个任务,格式是“分 时 日 月 周 命令”,例如 30 2 * * * /usr/local/bin/backup.sh 表示每天 2:30 执行备份脚本。cron 服务(RHEL 系叫 crond,Debian 系叫 cron)每分钟检查一次所有任务表,时间对上就执行。日常操作只需要四个参数:crontab -e 编辑,crontab -l 查看,crontab -r 删除,crontab -u 用户名 指定用户。写定时任务最容易出问题的往往不是时间格式,而是执行环境:cron 里的 PATH 很短、不读 .bashrc、% 有特殊含义,后文逐一说明。
crontab 参数说明:-e、-l、-r、-u
| 命令 | 作用 | 说明 |
|---|---|---|
crontab -e |
编辑当前用户的定时任务 | 用 VISUAL 或 EDITOR 环境变量指定的编辑器打开,保存退出后自动检查语法并安装 |
crontab -l |
查看当前用户的定时任务 | 没有任务时提示 no crontab for 用户名 |
crontab -r |
删除当前用户的全部定时任务 | 不做确认,直接清空 |
crontab -ri |
删除前先询问 | cronie 和 Debian 的 cron 都支持 -i |
crontab -u 用户 -l、crontab -u 用户 -e |
查看、编辑指定用户的任务 | 只有 root 能用 |
crontab 文件名 |
用文件内容替换当前用户的任务表 | 原有任务被整体覆盖 |
crontab -e 怎么编辑
- 执行
crontab -e。Ubuntu 第一次执行时会让你选择编辑器,选 nano 最简单;想换成 Vim,执行select-editor重新选,或者临时用EDITOR=vim crontab -e。 - 在文件末尾新起一行写任务,例如
*/5 * * * * /usr/local/bin/check.sh >> /var/log/check.log 2>&1。 - 保存退出。nano 按 Ctrl+O、回车保存,Ctrl+X 退出;Vim 输入
:wq。看到crontab: installing new crontab就说明已经生效,不需要重启 cron 服务。 - 格式写错时会提示
errors in crontab file, can't install,并询问是否重新编辑,回答 y 回去修改。 - 执行
crontab -l,确认任务已经在表里。
几条使用习惯:
- 改之前先备份。
-e和-r在键盘上挨着,敲错一个字母就清空了全部任务:crontab -l > ~/crontab.bak.$(date +%F)。 - 脚本里追加任务不用打开编辑器:
(crontab -l 2>/dev/null; echo '0 4 * * * /usr/local/bin/cleanup.sh') | crontab -。 - 不要直接改存放文件。 用户的任务表实际保存在
/var/spool/cron/用户名(RHEL 系)或/var/spool/cron/crontabs/用户名(Debian 系),要通过 crontab 命令修改,命令会检查语法,cron 也会在下一分钟识别到变化。 - 谁能用 crontab,由
/etc/cron.allow和/etc/cron.deny控制:存在 cron.allow 时,只有其中列出的用户可以使用。
五个时间字段怎么写
┌───────────── 分钟(0–59)
│ ┌─────────── 小时(0–23)
│ │ ┌───────── 日(1–31)
│ │ │ ┌─────── 月(1–12,也可写 jan–dec)
│ │ │ │ ┌───── 星期(0–7,0 和 7 都表示周日,也可写 sun–sat)
│ │ │ │ │
* * * * * 要执行的命令
四个特殊符号:
| 符号 | 含义 | 例子 |
|---|---|---|
* |
该字段的任意值 | 小时写 * 表示每个小时 |
, |
列举多个值 | 日写 1,15 表示 1 号和 15 号 |
- |
范围 | 小时写 9-18 表示 9 点到 18 点 |
/ |
步长 | 分钟写 */10 表示每 10 分钟;0-30/10 表示 0、10、20、30 分 |
常用写法速查:
| 表达式 | 含义 |
|---|---|
*/5 * * * * |
每 5 分钟 |
0 * * * * |
每小时整点 |
30 2 * * * |
每天 02:30 |
0 9-18 * * 1-5 |
周一到周五,9 点到 18 点的每个整点 |
0 3 * * 0 |
每周日 03:00 |
0 0 1 * * |
每月 1 日 00:00 |
0 4 1,15 * * |
每月 1 日和 15 日的 04:00 |
*/10 0-6 * * * |
0 点到 6 点之间每 10 分钟 |
@reboot |
cron 服务启动时执行一次,相当于开机执行 |
@hourly、@daily、@weekly、@monthly |
分别等于 0 * * * *、0 0 * * *、0 0 * * 0、0 0 1 * * |
三个容易写错的地方:
- 日和星期同时指定时是“或”的关系。
0 0 13 * 5不是“13 号并且是周五”,而是每月 13 号和每个周五都会执行。两个字段中有一个是*时,才只按另一个字段判断。 - 步长不会跨周期延续。 日写
*/7是每月的 1、8、15、22、29 号,到下个月又从 1 号开始,并不是严格的每 7 天一次。 - 没有“每月最后一天”的写法。 可以让 28 到 31 号都触发,再在命令里判断明天是不是 1 号:
0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /usr/local/bin/month-end.sh。
/etc/crontab、/etc/cron.d 与用户 crontab 的区别
| 位置 | 怎么修改 | 每行格式 | 以谁的身份执行 | 适合放什么 |
|---|---|---|---|---|
| 用户 crontab | crontab -e |
5 个时间字段 + 命令 | 任务表的所有者 | 某个用户或某个应用自己的任务 |
/etc/crontab |
root 直接编辑文件 | 5 个时间字段 + 用户名 + 命令 | 行内指定的用户 | 系统自带的调度,一般不动 |
/etc/cron.d/ 下的文件 |
root 新建文件 | 同 /etc/crontab | 行内指定的用户 | 自己的系统级任务,软件包、自动化部署写入的任务 |
/etc/cron.hourly、cron.daily、cron.weekly、cron.monthly |
放入可执行脚本 | 不是 crontab 格式,就是脚本本身 | root | 不在乎具体几点执行的日常维护 |
几个要点:
- 用户名字段是最常见的错误来源。 把
crontab -e里的写法原样贴进 /etc/crontab 或 /etc/cron.d,命令的第一个词会被当成用户名;反过来,把带用户名的行贴进 crontab -e,用户名会被当成命令去执行。 - 自己的系统级任务放 /etc/cron.d,不要改 /etc/crontab。 /etc/crontab 属于系统软件包的配置文件,升级时可能被提示覆盖;每个任务一个独立文件,也方便用配置管理工具下发和删除。
- Debian 系对文件名有要求。 /etc/cron.d 下的文件名,以及 cron.daily 等目录里的脚本名,只能由字母、数字、下划线和连字符组成,带点的会被忽略。最常见的坑是把
cleanup.sh放进 /etc/cron.daily,它永远不会执行;用run-parts --test /etc/cron.daily可以列出会被执行的脚本。/etc/cron.d 下的文件还要属于 root,且不能被其他用户写入。 - cron.daily 的执行时间不固定。 Debian 系由 /etc/crontab 在每天早上调度,装了 anacron 时交给 anacron;RHEL 系由 anacron 按 /etc/anacrontab 执行,默认只在 3 点到 22 点之间运行并加随机延迟,通常在凌晨 3 点多。需要精确时间的任务,写成 /etc/cron.d 里的具体时间。
一个 /etc/cron.d 文件的例子:
# /etc/cron.d/app-maint (文件名不要带点)
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
# 分 时 日 月 周 用户 命令
*/10 * * * * www-data /usr/local/bin/queue-check.sh >/dev/null 2>&1
15 3 * * * root /usr/local/bin/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1
写完执行 chmod 644 /etc/cron.d/app-maint,cron 会自动读取,不用重启服务。文件的最后一行要以换行结束,否则最后一个任务可能不会被识别。
运维常用的定时任务配置示例
日志切割
系统自带的 logrotate 每天会运行一次(由 cron.daily 或 systemd 的 logrotate.timer 触发),自己的应用日志建议在 /etc/logrotate.d/ 下写一份配置交给它。没有 logrotate 配置、又想按日期保存的 Nginx 访问日志,可以用 crontab 直接切割:
# 每天 00:00 把访问日志按昨天的日期改名,再让 Nginx 重新打开日志文件
0 0 * * * mv /var/log/nginx/access.log /var/log/nginx/access-$(date -d yesterday +\%F).log && kill -USR1 $(cat /run/nginx.pid)
USR1 信号让 Nginx 重新打开日志文件,不中断服务;pid 文件路径以 nginx.conf 里的 pid 配置为准。系统已经用 logrotate 管理 Nginx 日志时,不要再加这一条,两边会重复切割。
数据库备份
复杂的逻辑写进脚本,crontab 里只留一行调用,脚本里的 % 也就不用转义了:
#!/bin/bash
# /usr/local/bin/mysql-backup.sh
set -euo pipefail
DIR=/data/backup/mysql
mkdir -p "$DIR"
OUT="$DIR/all-$(date +%F).sql.gz"
mysqldump --defaults-extra-file=/root/.backup.cnf --single-transaction --routines --all-databases \
| gzip > "$OUT.tmp"
mv "$OUT.tmp" "$OUT"
find "$DIR" -name 'all-*.sql.gz' -mtime +7 -delete
# crontab -e 中加入:每天 02:30 执行,输出追加到日志
30 2 * * * /usr/local/bin/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1
set -o pipefail 让 mysqldump 失败时整条管道返回失败,配合 set -e 脚本立即退出:残缺的结果只留在 .tmp 文件里,不会被改名成正式备份,后面清理旧备份的 find 也不会执行;数据库账号写在权限为 600 的 /root/.backup.cnf 里,不要写进脚本或 crontab。find -mtime +7 按整天取整计算,删除的是至少 8 天前修改的文件。
清理临时文件、防止任务重叠、磁盘检查
# 每天 04:00 删除 /tmp/app 下超过 3 天未修改的文件
0 4 * * * find /tmp/app -type f -mtime +3 -delete
# 每 5 分钟同步一次;上一轮还没跑完时,这一轮直接跳过
*/5 * * * * flock -n /run/lock/sync.lock /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1
# 每 30 分钟检查根分区,使用率超过 85% 时写一条系统日志,便于监控采集
*/30 * * * * df -P / | awk 'NR==2 && $5+0 > 85 {print "root usage " $5}' | logger -t disk-check
flock 这一条很实用:同步、采集这类任务偶尔会因为网络慢而超时,如果不加锁,下一轮照常启动,几轮叠加下来会把机器拖垮。
crontab 不执行怎么排查
| 现象 | 原因 | 解决 |
|---|---|---|
手动执行正常,cron 里报 command not found |
cron 的 PATH 只有 /usr/bin:/bin 等少数目录,也不读 .bashrc、.bash_profile |
命令写绝对路径,或在任务表顶部写 PATH=... |
| 命令被截断、行为异常 | % 在 crontab 里会被换成换行,后面的内容变成命令的标准输入 |
写成 \%,或者把命令放进脚本 |
| 任务好像没执行,也看不到任何输出 | 输出默认以邮件发给用户,本机没有邮件服务时就丢了 | 命令末尾重定向 >> 日志文件 2>&1 |
脚本报 /bin/bash^M: bad interpreter |
脚本在 Windows 上编辑过,带 CRLF 换行 | sed -i 's/\r$//' 脚本名,并确认有执行权限 |
| /etc/cron.d 里的任务不执行 | 缺用户名字段、文件名带点(Debian 系)、权限不对、最后一行没有换行 | 按上一节的要求逐项检查 |
| 执行时间差了 8 小时 | 改时区后没有重启 cron 服务,仍按旧时区 | 重启 cron 服务 |
排查用到的命令:
systemctl status cron # Debian、Ubuntu;RHEL 系把 cron 换成 crond
grep CRON /var/log/syslog # Debian、Ubuntu 的执行记录;没有 syslog 文件时用 journalctl -u cron
tail -f /var/log/cron # RHEL 系的执行记录
# 用接近 cron 的精简环境手动跑一遍,能复现大部分环境变量问题
env -i SHELL=/bin/sh PATH=/usr/bin:/bin HOME=/root LOGNAME=root /bin/sh -c '/usr/local/bin/mysql-backup.sh'
日志里出现 CMD (/usr/local/bin/mysql-backup.sh),说明 cron 已经按时启动了命令,问题在命令本身,去看它自己的输出日志;连 CMD 记录都没有,说明时间写错了、任务表没装上,或者 cron 服务没在运行。
在管理系统里怎么落地
crontab 平时很少有人去看,入侵者却常在里面写一条定时下载脚本的任务来维持控制;人员交接时,也常常没人说得清某条任务是谁加的、做什么用。所以修改定时任务这类操作,要放在留痕的通道里完成。Toplink DCIM 的 IPMI 远程管理提供浏览器里的 SSH 控制台,每次会话自动录像,SSH 会话还记录命令级日志,谁在什么时候执行了 crontab -e、改了什么,事后可以回放核对。定时任务把服务器拖垮、SSH 登不上去时,同一页面的 VNC 控制台走 BMC 带外通道,可以登录本地终端把出问题的任务注释掉。
常见问题
crontab 能精确到秒吗?
不能,最小单位是分钟。要每 30 秒执行一次,可以写两行:* * * * * /usr/local/bin/job.sh 和 * * * * * sleep 30; /usr/local/bin/job.sh。更频繁、或者对时间精度有要求的任务,改用 systemd timer(OnUnitActiveSec 配合 AccuracySec)或常驻进程。
crontab -r 误删了全部任务,还能恢复吗?
crontab 没有回收站,只能从备份恢复。没有备份时可以从 cron 日志里找线索:Debian 系用 grep CRON /var/log/syslog 或 journalctl -u cron,RHEL 系看 /var/log/cron,CMD 记录列出了最近执行过的命令和用户,再按执行时间反推时间字段。以后修改前先用 crontab -l 备份一份。
服务器改了时区,crontab 按哪个时间执行?
cron 按 cron 服务进程的时区判断时间。用 timedatectl set-timezone Asia/Shanghai 改完时区后,重启 cron 服务(RHEL 系是 crond,Debian 系是 cron),新时区才会生效。RHEL 系的 cronie 还支持在任务表里写 CRON_TZ= 为这张表单独指定时区。
crontab 和 systemd timer 怎么选?
简单的周期任务用 crontab 足够。需要依赖其他服务、限制 CPU 和内存、错过执行时间后开机补跑(Persistent=true)、日志统一进 journal 时,用 systemd timer 更合适。两者可以并存,但同一个任务只在一处配置,避免重复执行。