┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ crond守护进程 │────▶│ 读取配置 │────▶│ 执行任务 │
│ (每分钟检查) │ │ crontab文件 │ │ 记录日志 │
└─────────────┘ └─────────────┘ └──────────────┘
/etc/crontab # 系统级定时任务
/etc/cron.d/ # 系统定时任务目录
/etc/cron.daily/ # 每日执行
/etc/cron.hourly/ # 每小时执行
/etc/cron.weekly/ # 每周执行
/etc/cron.monthly/ # 每月执行
/var/spool/cron/ # 用户crontab存储
/var/log/cron # cron日志
$ ls /etc/cron.d/
certbot e2scrub_all sgagenttask sysstat yunjing
$ ls /etc/cron.daily/
apport apt-compat aptitude dpkg logrotate man-db sysstat
# ┌──────── 分钟 (0-59)
# │ ┌────── 小时 (0-23)
# │ │ ┌──── 日 (1-31)
# │ │ │ ┌── 月 (1-12)
# │ │ │ │ ┌ 星期 (0-7, 0和7都表示周日)
# │ │ │ │ │
* * * * * command
| 符号 | 含义 | 示例 |
|---|---|---|
| * | 任意值 | * * * * * 每分钟 |
| , | 列举 | 0,30 * * * * 每小时的0分和30分 |
| - | 范围 | 0 9-17 * * * 9-17点每小时 |
| / | 步长 | */5 * * * * 每5分钟 |
*/5 * * * * /opt/health-check.sh # 每5分钟
0 2 * * * /opt/backup-db.sh # 每天凌晨2点
0 3 * * 1 /opt/clean-logs.sh # 每周一凌晨3点
0 0 1 * * /opt/monthly-report.sh # 每月1号0点
0 9 * * 1-5 /opt/daily-check.sh # 工作日每天9点
| 字符串 | 等价于 | 含义 |
|---|---|---|
| @yearly | 0 0 1 1 * | 每年一次 |
| @monthly | 0 0 1 * * | 每月一次 |
| @weekly | 0 0 * * 0 | 每周一次 |
| @daily | 0 0 * * * | 每天一次 |
| @hourly | 0 * * * * | 每小时一次 |
| @reboot | N/A | 每次启动后执行 |
$ crontab -l
*/5 * * * * flock -xn /tmp/stargate.lock -c '/usr/local/qcloud/stargate/admin/start.sh > /dev/null 2>&1 &'
crontab -e # 编辑当前用户的crontab
crontab -l # 查看当前用户的crontab
crontab -r # 删除当前用户的crontab
sudo crontab -e -u username # 编辑其他用户的
# /etc/crontab 多了一个"用户"字段
0 2 * * * root /opt/backup.sh
*/10 * * * * www-data /opt/check.sh
| 特性 | cron | systemd Timer |
|---|---|---|
| 精度 | 1分钟 | 1秒 |
| 错过执行 | 跳过 | 可配置追赶 |
| 依赖管理 | 无 | 有 |
| 日志 | 需手动 | 自动journal |
| 随机延迟 | 需脚本 | RandomizedDelaySec |
$ systemctl list-timers --all | head -10
NEXT LEFT LAST PASSED UNIT ACTIVATES Tue 2026-05-19 22:00:00 CST 37s Tue 2026-05-19 21:50:01 CST 9min ago sysstat-collect.timer sysstat-collect.service Tue 2026-05-19 22:35:30 CST 36min Tue 2026-05-19 21:37:26 CST 21min ago fwupd-refresh.timer fwupd-refresh.service Tue 2026-05-19 22:43:00 CST 43min Tue 2026-05-19 07:16:59 CST 14h ago apt-daily.timer apt-daily.service Wed 2026-05-20 00:00:00 CST 2h 0min Tue 2026-05-19 00:00:01 CST - dpkg-db-backup.timer dpkg-db-backup.service Wed 2026-05-20 00:00:00 CST 2h 0min Tue 2026-05-19 00:00:01 CST - logrotate.timer logrotate.service Wed 2026-05-20 00:07:00 CST 2h 7min - - sysstat-summary.timer sysstat-summary.service Wed 2026-05-20 01:44:36 CST 3h 45min Tue 2026-05-19 12:02:41 CST 9h ago motd-news.timer motd-news.service Wed 2026-05-20 02:20:59 CST 4h 21min Tue 2026-05-19 02:20:59 CST 19h ago update-notifier-download.timer update-notifier-download.service Wed 2026-05-20 02:30:52 CST 4h 31min Tue 2026-05-19 02:30:52 CST 19h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
# myapp.timer
[Unit]
Description=Run MyApp Backup daily
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
$ atq
at not installed
at 10:00 AM # 在指定时间执行
at now + 5 minutes # 5分钟后
at 3:00 AM tomorrow # 明天3点
atq # 查看待执行
atrm job_number # 删除任务
# 推荐的crontab写法
0 2 * * * flock -n /tmp/backup.lock /opt/backup.sh >> /var/log/backup.log 2>&1
# 脚本模板
#!/bin/bash
set -euo pipefail
LOG="/var/log/myapp-cron.log"
exec >> "$LOG" 2>&1
echo "$(date): Start"
/opt/myapp/task.sh
echo "$(date): Done"
# 1. 检查crond服务
systemctl status cron
# 2. 查看cron日志
grep CRON /var/log/syslog | tail -10
journalctl -u cron | tail -10
# 3. 检查环境变量
export PATH="/usr/local/bin:/usr/bin:/bin"
# 4. 检查权限
ls -la /opt/backup.sh
# 5. 手动执行脚本测试# 使用flock防止重复执行
flock -n /tmp/myapp.lock /opt/myapp/task.sh
# -n: 非阻塞,如果获取锁失败则退出
# 使用PID文件
PIDFILE=/tmp/myapp.pid
if [ -f "$PIDFILE" ]; then
if kill -0 $(cat "$PIDFILE") 2>/dev/null; then
echo "Already running"
exit 1
fi
fi
echo $$ > "$PIDFILE"
# ... 任务逻辑 ...
rm -f "$PIDFILE"
# 仅在工作日执行
0 9 * * 1-5 [ $(date +\%u) -le 5 ] && /opt/work-task.sh
# 仅在月初和月中执行
0 2 1,15 * * /opt/biweekly-task.sh
# 闰年2月29日
0 0 29 2 * /usr/bin/test $(date +\%Y) -eq $(date -d "next year" +\%Y) || /opt/leap-task.sh
多台服务器环境避免同一任务重复执行:
# 方案1:使用数据库锁
mysql -e "SELECT GET_LOCK('cron_task', 0)" | grep -q 1 && /opt/task.sh
# 方案2:使用Redis锁
redis-cli setnx cron:task $(hostname) && /opt/task.sh
# 方案3:指定执行节点
if [ "$(hostname)" = "cron-master" ]; then
/opt/task.sh
fi
# 1. 创建每5分钟输出时间的任务
(crontab -l 2>/dev/null; echo "*/5 * * * * date >> /tmp/cron-test.log") | crontab -
crontab -l
# 2. 等待几分钟后查看
cat /tmp/cron-test.log
# 3. 清理
crontab -r
rm /tmp/cron-test.log✅ 理解cron工作原理
✅ 掌握crontab时间表达式
✅ 学会系统级和用户级定时任务
✅ 了解systemd Timer替代方案
✅ 掌握定时任务调试与最佳实践
🎉 阶段二(用户与进程)完成!下一阶段:网络配置
#!/bin/bash
# cron-monitor.sh - 定时任务执行监控
# 检查所有定时任务的执行状态
echo "===== Cron任务执行报告 ====="
echo "时间: $(date)"
echo
# 列出所有用户crontab
echo "--- 用户crontab ---"
for user in $(cut -d: -f1 /etc/passwd); do
crontab=$(sudo crontab -l -u "$user" 2>/dev/null)
if [ -n "$crontab" ]; then
echo "用户: $user"
echo "$crontab"
echo
fi
done
# 检查系统crontab
echo "--- /etc/cron.d/ ---"
for file in /etc/cron.d/*; do
[ -f "$file" ] && echo "文件: $file" && grep -v "^#" "$file" | grep -v "^$"
echo
done
# 检查cron服务状态
echo "--- Cron服务状态 ---"
systemctl status cron --no-pager 2>/dev/null || systemctl status crond --no-pager
# 最近的cron日志
echo
echo "--- 最近cron执行记录 ---"
journalctl -u cron --since "1 hour ago" --no-pager | tail -10
anacron专为不持续运行的系统(如笔记本电脑、桌面)设计。如果系统在计划时间关机,anacron会在下次启动时补执行错过的任务。
# /etc/anacrontab
# 周期 延迟(分钟) 任务标识 命令
1 5 cron.daily run-parts /etc/cron.daily
7 25 cron.weekly run-parts /etc/cron.weekly
@monthly 45 cron.monthly run-parts /etc/cron.monthly
# 生产级定时任务模板
#!/bin/bash
set -euo pipefail
# 配置
TASK_NAME="database-backup"
LOCK_FILE="/tmp/$TASK_NAME.lock"
LOG_FILE="/var/log/$TASK_NAME.log"
TIMEOUT=3600 # 1小时超时
ALERT_WEBHOOK="https://hooks.slack.com/xxx"
# 日志函数
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') [$TASK_NAME] $*"; }
# 告警函数
alert() {
log "ALERT: $*"
curl -s -X POST "$ALERT_WEBHOOK" -d '{"text":"['"$(hostname)"'] '"$*"'"}' > /dev/null 2>&1 || true
}
# 加锁
exec 200>"$LOCK_FILE"
flock -n 200 || { log "Another instance is running"; exit 0; }
# 超时保护
timeout "$TIMEOUT" bash -c '
# 主任务逻辑
/opt/backup/db-dump.sh
/opt/backup/upload-s3.sh
' && log "Task completed successfully" || {
alert "Task failed with exit code $?"
exit 1
}