crontab —— 定时任务
crontab —— 定时任务
分类:05 服务与计划任务 适用:所有 Linux(cronie / vixie-cron / busybox cron) 一句话:备份、清理、巡检脚本的默认调度器。简单可靠,但坑也最多。
1. 它解决什么问题
- 每天凌晨备份数据库、清理日志。
- 每 5 分钟检查服务状态,异常时重启。
- 每月 1 号生成报表。
- 开机后自动拉起某个脚本。
2. 格式
* * * * * command
│ │ │ │ │
│ │ │ │ └── 星期 (0-7,0 和 7 都是周日)
│ │ │ └──── 月 (1-12)
│ │ └────── 日 (1-31)
│ └──────── 小时 (0-23)
└────────── 分钟 (0-59)
# 每天 2:30
30 2 * * * /opt/scripts/backup.sh
# 每 5 分钟
*/5 * * * * /opt/scripts/check.sh
# 每小时的第 0 分钟
0 * * * * /opt/scripts/hourly.sh
# 工作日 9 点
0 9 * * 1-5 /opt/scripts/report.sh
# 每月 1 号和 15 号 3 点
0 3 1,15 * * /opt/scripts/monthly.sh
# 每隔 2 小时(0,2,4...)
0 */2 * * * /opt/scripts/sync.sh
特殊字符串
| 写法 | 等价 |
|---|---|
@reboot |
开机后执行一次 |
@yearly / @annually |
0 0 1 1 * |
@monthly |
0 0 1 * * |
@weekly |
0 0 * * 0 |
@daily / @midnight |
0 0 * * * |
@hourly |
0 * * * * |
容易写错的时间表达
| 写法 | 实际含义 | 风险 |
|---|---|---|
* 2 * * * |
2 点内每分钟执行 | 以为只执行一次 |
0 2 * * * |
每天 2:00 一次 | 正确 |
0 0 31 2 * |
2 月 31 日,永不执行 | 日期不存在 |
0 0 * * 7 |
周日 | 0 与 7 都可 |
3. 管理命令
crontab -l # 查看当前用户
crontab -e # 编辑
crontab -r # 删除全部(⚠️ 无确认,先备份)
crontab -u nginx -l # 查看指定用户(需 root)
crontab -u nginx -e
crontab file.txt # 从文件加载
crontab -l > /tmp/cron.bak # 备份(重要!)
crontab -r没有任何确认提示,误触即清空。养成crontab -l > ~/cron.bak的习惯。
4. 四个配置位置的区别
| 位置 | 格式 | 说明 |
|---|---|---|
crontab -e |
5 段 + 命令 | 写入 /var/spool/cron/<user>,属于该用户 |
/etc/crontab |
5 段 + 用户名 + 命令 | 系统级,多一个 user 字段 |
/etc/cron.d/* |
同 /etc/crontab |
分包管理,推荐 Ansible 下发 |
/etc/cron.{hourly,daily,weekly,monthly}/ |
直接放可执行脚本 | 由 run-parts 调度,无需写时间 |
/etc/cron.d/app 示例(注意 user 字段):
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=""
*/10 * * * * app /opt/app/healthcheck.sh
在 /etc/crontab 或 /etc/cron.d 里漏写用户名,任务会静默不执行,这是最常见的坑之一。
5. 环境变量
cron 的环境极简,只有 HOME、LOGNAME、SHELL=/bin/sh、极简 PATH。这导致了 90% 的"手动能跑,cron 不跑"。
在 crontab 顶部显式声明:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=zh_CN.UTF-8
MAILTO=ops@example.com
脚本内部再兜底:
#!/bin/bash
set -euo pipefail
export PATH=/usr/local/bin:/usr/bin:/bin
export LANG=C.UTF-8
source /etc/profile # 需要时
6. 输出与日志
默认 cron 会把脚本的 stdout/stderr 通过邮件发给用户(MAILTO)。服务器通常没配 MTA,结果就是日志丢失。
推荐做法:脚本内自己重定向。
30 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
或 crontab 顶部:
MAILTO=""
查看 cron 自身日志:
# RHEL 系
tail -f /var/log/cron
journalctl -u crond -f
# Debian 系
grep CRON /var/log/syslog
journalctl -u cron -f
日志能看到"任务有没有被触发"以及执行的命令,是排查第一步。
7. 防止任务重叠(flock)
任务执行时间超过间隔时会叠在一起跑,造成资源争抢或数据错乱。
*/5 * * * * flock -n /var/run/check.lock /opt/scripts/check.sh
-n:拿不到锁立即退出(推荐)。-w 10:最多等 10 秒。
脚本内部也可以:
exec 9>/var/run/myscript.lock
flock -n 9 || { echo "already running"; exit 0; }
8. 实战场景
8.1 数据库备份(带日志与保留策略)
# crontab
0 2 * * * /opt/scripts/db-backup.sh >> /var/log/db-backup.log 2>&1
#!/bin/bash
set -euo pipefail
export PATH=/usr/local/bin:/usr/bin:/bin
DAY=$(date +%F)
DEST=/backup/mysql
mkdir -p "$DEST"
mysqldump --single-transaction --routines --triggers \
-u backup -p"$MYSQL_PWD" appdb | gzip > "$DEST/appdb-$DAY.sql.gz"
find "$DEST" -name '*.sql.gz' -mtime +14 -delete
8.2 服务健康检查与自愈
*/2 * * * * flock -n /var/run/health.lock /opt/scripts/health.sh
#!/bin/bash
curl -sf --max-time 5 http://127.0.0.1:8080/health >/dev/null && exit 0
echo "$(date) health check failed, restarting" >> /var/log/health.log
systemctl restart myapp
8.3 清理临时文件
0 4 * * * find /tmp -type f -atime +7 -delete
0 4 * * * find /var/log/app -name '*.log' -mtime +30 -delete
8.4 每月 1 号生成报表
0 8 1 * * /opt/scripts/monthly-report.sh
注意:1 号 0 点机器可能在跑其他任务,错开到 8 点更稳。
8.5 时区问题(跨时区业务)
CRON_TZ=UTC
0 2 * * * /opt/scripts/utc-job.sh
或脚本内 TZ=UTC date。CRON_TZ 需要 cronie 支持,busybox cron 不支持。
9. 常见坑(重点)
- PATH 不全:脚本里用了
docker、kubectl、java,cron 找不到。写全路径或在 crontab 里补 PATH。 - 相对路径失效:cron 的
HOME可能不是你以为的目录,脚本内全部用绝对路径,或先cd /opt/app。 %有特殊含义:crontab 里%表示换行,date +%F必须写成date +\%F,或把命令放进脚本。- 脚本没有执行权限:
chmod +x,或写成bash /path/script.sh。 - shebang 用了
#!/usr/bin/env bash:cron 的 PATH 里可能没有/usr/bin/env能找到的路径,直接写#!/bin/bash。 - 环境变量缺失:
JAVA_HOME、MYSQL_PWD、代理变量都要在脚本里显式设置。 - 忘记
2>&1:错误输出进了邮件黑洞,看不到失败原因。 - 任务重叠:加
flock。 crontab -r无确认:定期备份。/etc/cron.d缺 user 字段:任务静默不跑,日志里也没有明显报错。- 秒级需求:cron 最小粒度是分钟,需要秒级用
sleep拆分或 systemd timer(OnUnitActiveSec也支持秒)。 - 依赖网络/数据库:开机
@reboot时网络可能未就绪,加sleep 30或重试逻辑。 anacron差异:/etc/cron.daily在部分系统由anacron驱动,关机错过会在开机后补跑,时间不可预期。- SELinux 拦截:脚本放在非标准目录可能被拒,查
ausearch -m avc -ts recent。 /var/spool/cron权限:手工编辑该目录会破坏 cron,只用crontab -e。
10. cron vs systemd timer
| 能力 | cron | systemd timer |
|---|---|---|
| 最小粒度 | 1 分钟 | 秒级 |
| 错过补跑 | 需 anacron | Persistent=true 原生支持 |
| 日志 | /var/log/cron |
journalctl 统一 |
| 依赖管理 | 无 | After=、Requires= |
| 资源限制 | 无 | MemoryMax、CPUQuota |
| 随机延迟 | 手动 | RandomizedDelaySec |
| 可测试性 | 差 | systemctl start x.service 手动触发 |
新项目建议用 systemd timer,老环境继续 cron。
11. 速查表
crontab -l # 查看
crontab -e # 编辑
crontab -l > ~/cron.bak # 备份
crontab -u <user> -l # 指定用户
# 格式:分 时 日 月 周 命令
30 2 * * * /opt/backup.sh >> /var/log/backup.log 2>&1
*/5 * * * * flock -n /var/run/x.lock /opt/check.sh
0 9 * * 1-5 /opt/report.sh
@reboot /opt/start.sh
# 日志
tail -f /var/log/cron
journalctl -u cron -f
# 校验
systemctl status cron/crond
写 crontab 的三条铁律:
- 全部用绝对路径;
- 命令后面永远跟
>> /var/log/xxx.log 2>&1; - 长任务加
flock -n。