结论先行:cron 的 5 段式语法牢记「分 时 日 月 周 命令」,所有命令与脚本一律使用绝对路径,末尾必须附加 >> /var/log/job.log 2>&1 日志重定向。Linux 生产环境下 90% 的「定时任务不执行」均为 PATH 缺失或相对路径失效所致。无状态单机轮询使用 cron 最简便,需要开机补跑与复杂依赖管理的业务推荐使用 systemd timer。
1、你能搞定啥
看完这篇你能:
- 掌握 cron 的 5 段式语法规则,看懂任何调度时间表达式
- 直接复制生产可用的定时任务模板(数据库备份、日志轮转、证书更新)
- 彻底排查任务不触发的三大根因:环境变量缺失、相对路径失效、脚本权限不足
- 搞清 cron 与 systemd timer 的选型边界
2、为什么需要定时任务
出海独立开发者的云端服务器上,有大量周期性自动化作业:
| 场景 |
典型任务 |
运行频率 |
核心价值 |
| 数据备份 |
导出 PostgreSQL / MySQL 数据库快照 |
每天凌晨 02:00 |
灾难恢复防丢数据 |
| 日志归档 |
轮转压缩 Nginx、Docker 容器日志 |
每周日凌晨 03:00 |
防止磁盘被写满导致死机 |
| 服务巡检 |
探活关键 API 与容器健康状态 |
每 5 分钟 |
第一时间捕获进程崩溃 |
| 证书续期 |
自动检查并签发 ACME SSL 证书 |
每月 1 号凌晨 00:00 |
避免网站 HTTPS 过期失效 |
| 代码同步 |
自动化拉取静态站点或配置文件 |
每天早上 08:00 |
保障发布环境与主干同步 |
这些周期工作绝不能依赖手工操作,更不能写个死循环加 sleep 常驻内存。cron 是 Linux 发行版自带的标准调度工具,资源占用低且稳定可靠。
3、crontab 基础命令速查
常用操作命令
1 2 3 4 5 6 7 8 9 10 11
| crontab -e
crontab -l
crontab -r
sudo crontab -u www-data -e
|
管理 cron 系统守护进程
1 2 3 4 5 6 7
| sudo systemctl status cron sudo systemctl restart cron
sudo systemctl status crond sudo systemctl restart crond
|
4、cron 5 段式语法图解
一个标准的 cron 任务行包含 5 个时间占位符以及执行命令:
常用符号与取值规则
| 符号 |
含义 |
实际示例 |
运行语义 |
* |
匹配该字段的所有可能值 |
* * * * * |
每分钟都执行一次 |
*/n |
每隔 n 个时间单位 |
*/15 * * * * |
0分、15分、30分、45分各执行一次 |
- |
连续闭合范围 |
0 9-17 * * * |
早上 9 点到下午 17 点之间的每个整点执行 |
, |
离散取值枚举 |
0 8,12,20 * * * |
每天 08:00、12:00、20:00 各执行一次 |
特殊宏时间别名
现代 Linux 发行版(Vixie cron)支持预定义别名,可直接替换 5 个时间参数:
1 2 3 4 5 6
| @yearly @monthly @weekly @daily @hourly @reboot
|
[!NOTE]
尽管 @daily 等宏更加直观,但老旧嵌入式 Linux 或 Alpine 等 BusyBox 环境可能缺乏完整支持。需要跨多环境迁移的脚本,建议坚持书写标准的 5 段式数字格式。
5、常用生产场景实战模板
你可以直接根据业务需求复用以下标准模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
30 2 * * * /usr/bin/mysqldump --defaults-extra-file=/root/.my.cnf production_db 2>> /var/log/db_backup.err | gzip > /opt/backups/db_$(date +\%Y\%m\%d).sql.gz
30 3 * * * /usr/bin/find /opt/backups/ -type f -name "*.sql.gz" -mtime +7 -delete >> /var/log/cleanup.log 2>&1
*/5 * * * * /opt/scripts/health-check.sh >> /var/log/health-check.log 2>&1
0 4 * * 1 /usr/bin/docker image prune -af >> /var/log/docker-prune.log 2>&1
0 9 * * 1-5 /usr/bin/python3 /opt/scripts/send_daily_digest.py >> /var/log/digest.log 2>&1
|
6、排查「任务不执行」的三大陷阱
生产运维中最常遇到的常见卡点是:“脚本在终端手动敲能够正常运行,丢进 crontab 却完全没有反应”。90% 以上由以下三大原因引起:
陷阱 1:PATH 环境变量极其简陋
当你登录交互式终端时,Shell 会加载 /etc/profile、~/.bashrc、~/.zshrc 等配置文件,你的 PATH 包含 /usr/local/bin、/home/user/.nvm/... 等大量路径。
而在 cron 环境中,默认的 PATH 通常仅为 /usr/bin:/bin。如果你的脚本调用了 node、docker、aws 或自定义脚本,cron 将直接报错 command not found。
解决方案:在 crontab 文件的最顶端强制声明环境变量:
1 2 3 4 5 6 7 8
| SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin MAILTO="" HOME=/root
0 2 * * * /usr/bin/python3 /opt/scripts/report.py >> /var/log/report.log 2>&1
|
陷阱 2:相对路径导致找不到文件
cron 执行命令时的工作目录默认是执行用户的 $HOME,而不是脚本所在的目录。如果脚本内部包含 cat ./config.json 或 sqlite3 db.sqlite,会导致定位失败。
解决方案:
- cron 任务行中必须使用脚本的绝对路径。
- 在 Shell 脚本内部首行先切换到脚本所在目录:
1 2 3 4 5 6 7 8 9
| #!/usr/bin/env bash set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}"
python3 main.py --config ./config.json
|
陷阱 3:百分号 % 未转义
在 cron 语法中,百分号 % 具有特殊语义(代表换行符),后续所有内容都会作为标准输入传递给前面的命令。
例如以下命令会在执行时直接中断:
1 2
| 0 2 * * * /usr/bin/mysqldump db > /backup/db_$(date +%Y%m%d).sql
|
解决方案:在 crontab 中必须对 % 加反斜杠转义:
1 2
| 0 2 * * * /usr/bin/mysqldump db > /backup/db_$(date +\%Y\%m\%d).sql
|
或者将带日期的逻辑封装进独立的 .sh 脚本内,在 crontab 中仅调用该脚本。
7、日志输出与调试技巧
默认情况下,cron 会尝试将脚本的标准输出和错误通过本地邮件服务发送给用户。如果系统未配置本地 MTA(如 Postfix/Sendmail),这些输出会被直接丢弃。
生产日志重定向标准
为每一个 cron 任务添加标准输出与标准错误重定向:
1 2 3 4 5 6 7 8
| 0 3 * * * /opt/scripts/sync.sh > /var/log/sync.log 2>&1
0 3 * * * /opt/scripts/sync.sh >> /var/log/sync.log 2>&1
0 3 * * * /opt/scripts/sync.sh > /dev/null 2>> /var/log/sync.err
|
查看系统原生调度日志
当任务连日志文件都没有生成时,说明任务压根没被调度。检查系统主日志:
1 2 3 4 5
| grep CRON /var/log/syslog | tail -n 30
tail -n 30 /var/log/cron
|
8、实战场景:防重叠执行与并发锁
某些耗时较长的脚本(如大表数据同步、大型归档),如果上一次执行尚未结束,下一次调度时间已到,会导致多个实例并行运行,迅速耗尽服务器内存与 CPU。
使用 flock 加排他锁
Linux 自带的 flock 命令能为定时任务提供轻量级进程锁:
1 2
| */10 * * * * /usr/bin/flock -n /tmp/backup.lock /opt/scripts/heavy-sync.sh >> /var/log/heavy-sync.log 2>&1
|
-n(non-block):获取不到文件锁时立即退出,避免排队积压。
- 任务执行完毕后,文件描述符关闭,锁会自动释放,极其可靠。
9、进阶选择:何时该用 systemd timer?
当业务规模上升,任务之间存在前置依赖,或者要求服务器关机开机后能够自动“补跑”错过的定时任务时,systemd timer 是更现代化的高阶方案。
| 特性维度 |
crontab |
systemd timer |
| 配置门槛 |
单行文本,上手极快 |
需编写 .service 与 .timer 两个单元文件 |
| 最小调度精度 |
1 分钟(无秒级支持) |
支持毫秒级与单调时间(OnUnitActiveSec=) |
| 关机漏跑补跑 |
不支持(开机后跳过) |
原生支持(声明 Persistent=true) |
| 依赖关系编排 |
不支持 |
原生支持(After= / Wants=) |
| 日志聚合 |
依赖手工 >> 重定向 |
自动接入 journalctl 结构化日志 |
| 资源用量限制 |
难以针对单个任务限额 |
原生支持 MemoryMax= / CPUQuota= |
实操判断基准:
- 无状态的小型轮询脚本(如清理
/tmp、打卡通知、静态文件拉取):继续使用 cron,轻便透明。
- 核心数据备份与关键业务批处理:推荐升级至 systemd timer,配置
Persistent=true 避免停机维护期间漏跑。
10、实战配置落地清单
在将新的 cron 任务部署到生产服务器前,按以下清单核验:
- [ ] 执行环境:crontab 文件顶部已声明
PATH 与 SHELL=/bin/bash
- [ ] 全绝对路径:命令本身(如
/usr/bin/python3)与参数文件均使用完整绝对路径
- [ ] 权限完备:脚本已赋予执行权限(
chmod +x /opt/scripts/job.sh),且所有者属组正确
- [ ] 日志捕获:每条任务行尾均追加了
>> /var/log/job.log 2>&1
- [ ] 特殊字符转义:时间与命令中的
% 已使用 \% 进行转义
- [ ] 并发保护:高频或长耗时任务已通过
flock -n 防止多进程堆叠
- [ ] 预先调试验证:已在终端手动以目标用户身份(如
sudo -u www-data /bin/bash /opt/scripts/job.sh)完整跑通一次