服务器定时任务完全指南:crontab语法、常见陷阱与systemd timer对比

服务器定时任务完全指南: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,你在终端能直接用的命令(如 nodepython3docker)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 直连大陆,支付宝付款即可开通。

Telegram