服务器定时任务完全指南:crontab语法、常见陷阱与systemd timer对比
定时任务写好了却不执行?你可能踩了这些坑
crontab 是 Linux 服务器上使用最广泛的定时任务工具,但它有一些不直观的行为,导致很多人写好的任务总是不执行或执行结果不符合预期。本文把 crontab 的完整用法和常见坑一次说清楚,并介绍更现代的 systemd timer 方案。
一、crontab 语法完整解析
# crontab 基本格式:
# 分钟 小时 日期 月份 星期 命令
# * * * * * command
# 字段范围:
# 分钟:0–59
# 小时:0–23
# 日期:1–31
# 月份:1–12
# 星期:0–7(0 和 7 都代表周日)
常用示例速查
# 每分钟执行
* * * * * /path/to/script.sh
# 每天凌晨 3:00 执行
0 3 * * * /path/to/backup.sh
# 每周一凌晨 2:30 执行
30 2 * * 1 /path/to/weekly.sh
# 每月 1 日和 15 日的 8:00 执行
0 8 1,15 * * /path/to/monthly.sh
# 每隔 5 分钟执行一次
*/5 * * * * /path/to/monitor.sh
# 工作日(周一到周五)的 9:00–18:00 每小时执行
0 9-18 * * 1-5 /path/to/workhours.sh
# 每小时的第 0、15、30、45 分钟执行
0,15,30,45 * * * * /path/to/quarter.sh
二、编辑和管理 crontab
# 编辑当前用户的 crontab
crontab -e
# 查看当前用户的 crontab
crontab -l
# 删除当前用户的全部 crontab
crontab -r
# 编辑指定用户的 crontab(需要 root)
sudo crontab -u www-data -e
# 系统级 crontab(/etc/crontab 和 /etc/cron.d/ 目录)
sudo nano /etc/crontab
# 系统级 crontab 多一个用户字段:
# 分 时 日 月 周 用户 命令
0 3 * * * root /usr/local/bin/backup.sh
三、最常见的 5 个坑
坑 1:环境变量与交互式 Shell 不同
cron 运行时的环境变量极其简单,PATH 只有 /usr/bin:/bin,你在终端能直接用的命令(如 node、python3、docker)cron 可能找不到。
# 错误写法(node 找不到)
* * * * * node /home/user/app.js
# 正确写法一:使用完整路径
* * * * * /usr/bin/node /home/user/app.js
# 正确写法二:在 crontab 顶部定义 PATH
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
* * * * * node /home/user/app.js
# 查找命令的完整路径
which node # 输出 /usr/bin/node
which python3 # 输出 /usr/bin/python3
坑 2:脚本没有执行权限
# 确认脚本有执行权限
chmod +x /path/to/script.sh
# 或者在 crontab 中显式调用解释器(不需要执行权限)
* * * * * /bin/bash /path/to/script.sh
* * * * * /usr/bin/python3 /path/to/script.py
坑 3:工作目录问题
cron 执行时的工作目录是 $HOME,不是脚本所在目录。如果脚本中有相对路径,会找不到文件:
# 在 crontab 中先切换目录
* * * * * cd /home/user/myapp && node app.js
# 或在脚本内部使用 cd
#!/bin/bash
cd "$(dirname "$0")" # 切换到脚本所在目录
./run.sh
坑 4:输出没有重定向导致发邮件
cron 会把命令的 stdout/stderr 通过 sendmail 发送邮件,如果服务器没配邮件系统会产生错误:
# 将输出重定向到日志文件
* * * * * /path/to/script.sh >> /var/log/cron_script.log 2>&1
# 丢弃所有输出(不需要日志时)
* * * * * /path/to/script.sh > /dev/null 2>&1
坑 5:时区与服务器时区不一致
# 查看服务器当前时区
timedatectl
# 在 crontab 中指定时区
CRON_TZ=Asia/Shanghai
0 9 * * * /path/to/morning_report.sh
四、查看 cron 执行日志
# Ubuntu/Debian 查看 cron 日志
grep CRON /var/log/syslog | tail -20
# 或通过 journald 查看
sudo journalctl -u cron --since "1 hour ago"
# 查看特定用户的 cron 执行记录
sudo grep "CMD" /var/log/syslog | grep "root"
五、systemd timer:更现代的定时任务方案
systemd timer 相比 crontab 有几个优势:支持执行失败后自动重试、依赖其他 systemd 服务、日志集成到 journald、支持更复杂的时间表达式。
# 创建服务文件(定义要执行的任务)
sudo nano /etc/systemd/system/myreport.service
[Unit]
Description=Daily Report Generator
[Service]
Type=oneshot
User=youruser
WorkingDirectory=/home/youruser/myapp
ExecStart=/usr/bin/python3 /home/youruser/myapp/report.py
StandardOutput=journal
StandardError=journal
# 创建 timer 文件(定义执行时间)
sudo nano /etc/systemd/system/myreport.timer
[Unit]
Description=Run daily report at 8:00 AM
[Timer]
OnCalendar=*-*-* 08:00:00 # 每天 8:00 执行
RandomizedDelaySec=300 # 随机延迟 0–5 分钟(防止多服务同时启动)
Persistent=true # 如果错过执行时间,下次启动时补跑
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable myreport.timer
sudo systemctl start myreport.timer
# 查看所有 timer 状态
systemctl list-timers
# 查看执行日志
journalctl -u myreport.service
crontab vs systemd timer 选择建议
| 场景 | 推荐方案 |
|---|---|
| 简单的定期脚本(备份、清理) | crontab,配置更简单 |
| 需要依赖其他服务启动后才执行 | systemd timer |
| 需要详细日志和失败告警 | systemd timer(集成 journald) |
| 需要错过执行时间后补跑 | systemd timer(Persistent=true) |
| 需要并发控制(防止任务重叠执行) | systemd timer(默认防止并发) |
总结
crontab 的核心注意点:使用命令完整路径 → 脚本有执行权限 → 脚本内部用绝对路径 → 重定向输出到日志文件 → 检查时区设置。遇到任务不执行时,第一步看 /var/log/syslog 中的 cron 日志。对于复杂场景,systemd timer 提供了更强大的控制能力和更好的日志集成。
需要稳定运行定时任务的服务器,IDC.Net 香港云服务器首月 10 元起,7×24 小时稳定运行,CN2 GIA 直连大陆,支付宝付款即可开通。