
Crontab定时任务避坑指南:这8个坑我都帮你踩过了
一、概述
1.1 为什么要写这篇文章
干SRE这行十年了,要说让我最头疼的东西,crontab绝对能排进前三。不是因为它难用,恰恰相反,它太简单了,简单到让人掉以轻心。
记得2019年那会儿,我刚跳槽到一家电商公司。入职第二天凌晨三点,手机被打爆了——生产环境的数据同步任务没跑,导致早高峰的时候用户看到的全是昨天的价格。排查了两个小时,最后发现就是因为脚本里用了一个命令,在交互式shell里能跑,放到crontab里就找不到。PATH环境变量的问题,一个新人级别的错误,差点让我试用期都过不了。
这些年我踩过的crontab的坑,说出来都是泪。今天把这些经验整理出来,希望能帮后来人少走点弯路。
1.2 crontab是什么
crontab是Unix/Linux系统下的定时任务调度器,全称是"cron table"。cron这个名字来源于希腊语"chronos",意思是时间。它从1975年诞生至今,已经快50年了,依然是Linux系统下最常用的定时任务工具。
简单来说,crontab就是一个"闹钟管理器"。你告诉它什么时间做什么事,它就会准时执行。比如:
每天凌晨2点备份数据库 每5分钟检查一次服务状态 每周一早上9点发送周报邮件 每月1号统计上月的业务数据
1.3 技术特点
crontab之所以能活到今天,还这么受欢迎,主要有这几个特点:
轻量级:cron守护进程常驻内存,资源占用极低,在我测试的环境中,crond进程通常只占用几MB内存。
可靠性高:只要系统不宕机,cron就会按时触发任务。它经过了几十年的考验,稳定性毋庸置疑。
配置简单:五个时间字段加一个命令,一行搞定。这种简洁性是把双刃剑,简单意味着功能有限。
用户隔离:每个用户有自己的crontab文件,互不干扰。root用户还可以管理所有用户的定时任务。
日志追踪:任务执行会记录到系统日志,出了问题可以追溯。
1.4 适用场景
根据我这些年的经验,crontab特别适合以下场景:
定期维护任务:
日志清理和轮转 临时文件清理 数据库优化(VACUUM、索引重建) 证书续期检查
数据处理任务:
数据备份 数据同步 报表生成 ETL作业
监控和告警:
服务健康检查 磁盘空间监控 进程存活检测 业务指标采集
业务相关任务:
定时发送邮件/短信 订单超时处理 缓存预热 数据归档
但是,crontab也有它不擅长的地方:
需要秒级精度的任务(crontab最小粒度是分钟) 需要复杂依赖关系的任务链 需要任务失败重试的场景 分布式环境下的任务调度
对于这些场景,你可能需要考虑其他方案,比如systemd timer、Kubernetes CronJob、或者专业的调度系统如Airflow、XXL-JOB等。
1.5 环境要求
本文的所有操作和示例基于以下环境:
操作系统:CentOS 7/8、Rocky Linux 8/9、Ubuntu 20.04/22.04
cron版本:cronie 1.5.x 或 vixie-cron
Shell:Bash 4.x+
在开始之前,先确认你的系统已经安装并启动了cron服务:
# CentOS/RHEL/Rocky Linux
systemctl status crond
# Ubuntu/Debian
systemctl status cron
如果服务没有运行,使用以下命令启动:
# CentOS/RHEL/Rocky Linux
sudo systemctl start crond
sudo systemctl enable crond
# Ubuntu/Debian
sudo systemctl start cron
sudo systemctl enable cron
二、详细步骤
2.1 准备工作
在开始配置crontab之前,有几件事情必须做好。这是我多年踩坑总结出来的经验,跳过任何一步都可能给你埋下隐患。
2.1.1 确认系统时间和时区
这个是最容易被忽视的,但却是最重要的。我见过太多次因为时区问题导致任务提前或延后执行的故障了。
# 查看当前时间和时区
date
timedatectl
# 查看时区设置
cat /etc/timezone # Ubuntu/Debian
ls -l /etc/localtime # CentOS/RHEL
# 如果需要修改时区(以上海为例)
sudo timedatectl set-timezone Asia/Shanghai
关于NTP时间同步,这个也很重要。如果服务器时间漂移严重,定时任务可能会出现诡异的问题:
# 检查NTP同步状态
timedatectl show | grep NTP
# CentOS 7+
sudo systemctl status chronyd
chronyc tracking
# Ubuntu
sudo systemctl status systemd-timesyncd
2.1.2 了解crontab文件位置
在Linux系统中,crontab相关的文件和目录有好几个,搞清楚它们的区别很重要:
/etc/crontab # 系统级crontab,需要指定用户
/etc/cron.d/ # 系统级crontab目录,放置各种系统任务
/etc/cron.hourly/ # 每小时执行的脚本目录
/etc/cron.daily/ # 每天执行的脚本目录
/etc/cron.weekly/ # 每周执行的脚本目录
/etc/cron.monthly/ # 每月执行的脚本目录
/var/spool/cron/ # 用户级crontab文件存放目录(CentOS/RHEL)
/var/spool/cron/crontabs/ # 用户级crontab文件存放目录(Ubuntu/Debian)
普通用户一般使用crontab -e命令编辑自己的定时任务,文件存放在/var/spool/cron/目录下,以用户名命名。
2.1.3 检查用户权限
不是所有用户都能使用crontab的。系统通过两个文件控制权限:
/etc/cron.allow # 白名单,只有列在里面的用户可以使用crontab
/etc/cron.deny # 黑名单,列在里面的用户禁止使用crontab
规则是这样的:
如果 cron.allow存在,只有里面的用户可以使用crontab如果 cron.allow不存在但cron.deny存在,除了里面的用户都可以使用如果两个文件都不存在,通常只有root可以使用(不同发行版行为可能不同)
# 检查当前用户是否可以使用crontab
crontab -l
# 如果提示permission denied,需要联系管理员
# 管理员可以这样添加用户权限
echo"username" | sudo tee -a /etc/cron.allow
2.2 核心配置
2.2.1 crontab时间表达式
crontab的时间表达式由5个字段组成,这是整个crontab的核心:
┌───────────── 分钟 (0 - 59)
│ ┌───────────── 小时 (0 - 23)
│ │ ┌───────────── 日期 (1 - 31)
│ │ │ ┌───────────── 月份 (1 - 12)
│ │ │ │ ┌───────────── 星期 (0 - 7,0和7都表示星期日)
│ │ │ │ │
│ │ │ │ │
* * * * * command
每个字段支持的特殊字符:
* | * * * * * | |
, | 1,15,30 * * * * | |
- | 0 9-18 * * * | |
/ | */5 * * * * |
我来举一些常用的例子:
# 每分钟执行
* * * * * /path/to/script.sh
# 每5分钟执行
*/5 * * * * /path/to/script.sh
# 每小时的第30分钟执行
30 * * * * /path/to/script.sh
# 每天凌晨2点执行
0 2 * * * /path/to/script.sh
# 每天的9点和18点执行
0 9,18 * * * /path/to/script.sh
# 工作日的上午9点到下午6点,每小时执行
0 9-18 * * 1-5 /path/to/script.sh
# 每周一凌晨3点执行
0 3 * * 1 /path/to/script.sh
# 每月1号凌晨4点执行
0 4 1 * * /path/to/script.sh
# 每年1月1日凌晨0点执行
0 0 1 1 * /path/to/script.sh
# 每季度第一天执行(1月、4月、7月、10月的1号)
0 0 1 1,4,7,10 * /path/to/script.sh
有个小技巧:如果你记不住时间表达式的格式,可以用在线工具,比如 crontab.guru 这个网站,输入表达式就能看到解释。
2.2.2 编辑crontab
使用crontab -e命令进入编辑模式:
# 编辑当前用户的crontab
crontab -e
# root用户编辑其他用户的crontab
sudo crontab -e -u username
第一次使用时,系统可能会让你选择编辑器。我个人推荐vim,但如果你不熟悉vim,选择nano也可以。
可以通过环境变量指定默认编辑器:
export EDITOR=vim
# 或者永久设置
echo'export EDITOR=vim' >> ~/.bashrc
2.2.3 crontab常用命令
# 列出当前用户的所有定时任务
crontab -l
# 编辑定时任务
crontab -e
# 删除所有定时任务(危险操作!)
crontab -r
# 删除前确认(推荐)
crontab -ri
# root查看其他用户的定时任务
sudo crontab -l -u username
# 从文件导入crontab
crontab /path/to/crontab_file
# 备份当前crontab到文件
crontab -l > ~/crontab_backup_$(date +%Y%m%d).txt
2.3 启动验证
配置完crontab后,一定要验证它是否正常工作。我的做法是先添加一个测试任务:
# 编辑crontab
crontab -e
# 添加一个每分钟执行的测试任务
* * * * * echo"crontab test at $(date)" >> /tmp/crontab_test.log
等待1-2分钟,然后检查日志:
# 检查测试日志
cat /tmp/crontab_test.log
# 检查系统日志(CentOS/RHEL)
sudo grep CRON /var/log/cron
# 检查系统日志(Ubuntu/Debian)
sudo grep CRON /var/log/syslog
如果看到日志输出,说明crontab工作正常。记得测试完成后删除测试任务:
crontab -e
# 删除测试任务那一行
三、示例代码和配置
3.1 完整配置示例
这是我在生产环境中常用的一个crontab配置模板,包含了各种最佳实践:
# ============================================
# Crontab Configuration for Production Server
# Author: SRE Team
# Last Updated: 2025-01-07
# ============================================
# 环境变量设置(重要!)
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
HOME=/home/deploy
# ============================================
# 系统维护任务
# ============================================
# 每天凌晨3点清理7天前的日志文件
0 3 * * * /usr/bin/find /var/log/app -name "*.log" -mtime +7 -delete 2>&1 | /usr/bin/logger -t cron-log-cleanup
# 每天凌晨4点清理临时文件
0 4 * * * /usr/bin/find /tmp -type f -mtime +3 -delete 2>&1 | /usr/bin/logger -t cron-tmp-cleanup
# ============================================
# 数据库备份任务
# ============================================
# 每天凌晨2点全量备份MySQL数据库
0 2 * * * /usr/bin/flock -xn /tmp/mysql_backup.lock /home/deploy/scripts/mysql_backup.sh >> /var/log/backup/mysql_backup.log 2>&1
# 每周日凌晨1点备份到远程存储
0 1 * * 0 /home/deploy/scripts/remote_backup.sh >> /var/log/backup/remote_backup.log 2>&1
# ============================================
# 应用监控任务
# ============================================
# 每分钟检查应用健康状态
* * * * * /home/deploy/scripts/health_check.sh >> /var/log/monitor/health_check.log 2>&1
# 每5分钟检查磁盘空间
*/5 * * * * /home/deploy/scripts/disk_check.sh 2>&1 | /usr/bin/logger -t cron-disk-check
# ============================================
# 业务定时任务
# ============================================
# 每天早上8点发送日报
0 8 * * 1-5 /usr/bin/flock -xn /tmp/daily_report.lock /home/deploy/scripts/send_daily_report.sh >> /var/log/report/daily.log 2>&1
# 每月1号凌晨5点生成月报
0 5 1 * * /home/deploy/scripts/generate_monthly_report.sh >> /var/log/report/monthly.log 2>&1
# ============================================
# 数据同步任务
# ============================================
# 每10分钟同步缓存数据
*/10 * * * * /usr/bin/flock -xn /tmp/cache_sync.lock /home/deploy/scripts/sync_cache.sh >> /var/log/sync/cache.log 2>&1
# 每小时同步配置文件
0 * * * * /home/deploy/scripts/sync_config.sh >> /var/log/sync/config.log 2>&1
3.2 实际应用案例
案例1:MySQL数据库自动备份脚本
这是我在生产环境实际使用的备份脚本,经过多年优化:
#!/bin/bash
# mysql_backup.sh - MySQL自动备份脚本
# 用法: ./mysql_backup.sh
set -euo pipefail
# ========== 配置区域 ==========
MYSQL_USER="backup_user"
MYSQL_PASSWORD="your_secure_password"
MYSQL_HOST="localhost"
BACKUP_DIR="/data/backup/mysql"
RETAIN_DAYS=7
DATE=$(date +%Y%m%d_%H%M%S)
HOSTNAME=$(hostname)
# 邮件告警配置
ALERT_EMAIL="dba@company.com"
# ========== 函数定义 ==========
log() {
echo"[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
send_alert() {
local subject="[ALERT] MySQL Backup Failed on ${HOSTNAME}"
local body="$1"
echo"${body}" | mail -s "${subject}""${ALERT_EMAIL}"
}
cleanup_old_backups() {
log"Cleaning up backups older than ${RETAIN_DAYS} days..."
find "${BACKUP_DIR}" -name "*.sql.gz" -mtime +${RETAIN_DAYS} -delete
find "${BACKUP_DIR}" -name "*.sql.gz.md5" -mtime +${RETAIN_DAYS} -delete
}
# ========== 主逻辑 ==========
log"Starting MySQL backup..."
# 创建备份目录
mkdir -p "${BACKUP_DIR}"
# 获取所有数据库列表(排除系统库)
DATABASES=$(mysql -h${MYSQL_HOST} -u${MYSQL_USER} -p${MYSQL_PASSWORD} -N -e \
"SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys');" 2>/dev/null)
if [ -z "${DATABASES}" ]; then
log"ERROR: No databases found or connection failed"
send_alert "Failed to connect to MySQL or no databases found"
exit 1
fi
# 备份每个数据库
for DB in${DATABASES}; do
BACKUP_FILE="${BACKUP_DIR}/${DB}_${DATE}.sql.gz"
log"Backing up database: ${DB}"
if mysqldump -h${MYSQL_HOST} -u${MYSQL_USER} -p${MYSQL_PASSWORD} \
--single-transaction \
--routines \
--triggers \
--events \
--set-gtid-purged=OFF \
"${DB}" 2>/dev/null | gzip > "${BACKUP_FILE}"; then
# 生成MD5校验和
md5sum "${BACKUP_FILE}" > "${BACKUP_FILE}.md5"
# 验证备份文件大小
FILE_SIZE=$(stat -f%z "${BACKUP_FILE}" 2>/dev/null || stat -c%s "${BACKUP_FILE}")
if [ "${FILE_SIZE}" -lt 100 ]; then
log"WARNING: Backup file ${BACKUP_FILE} seems too small (${FILE_SIZE} bytes)"
else
log"SUCCESS: ${DB} backed up to ${BACKUP_FILE} (${FILE_SIZE} bytes)"
fi
else
log"ERROR: Failed to backup ${DB}"
send_alert "Failed to backup database: ${DB}"
fi
done
# 清理旧备份
cleanup_old_backups
log"Backup completed!"
# 输出备份统计
TOTAL_SIZE=$(du -sh "${BACKUP_DIR}" | cut -f1)
BACKUP_COUNT=$(find "${BACKUP_DIR}" -name "*_${DATE}.sql.gz" | wc -l)
log"Total backup size: ${TOTAL_SIZE}, Files created today: ${BACKUP_COUNT}"
案例2:服务健康检查脚本
#!/bin/bash
# health_check.sh - 服务健康检查脚本
set -uo pipefail
# ========== 配置 ==========
SERVICES=("nginx""php-fpm""redis""mysql")
HTTP_ENDPOINTS=(
"http://localhost/health"
"http://localhost:8080/api/health"
)
ALERT_WEBHOOK="https://hooks.slack.com/services/xxx/yyy/zzz"
LOG_FILE="/var/log/monitor/health_check.log"
# ========== 函数 ==========
log() {
echo"[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "${LOG_FILE}"
}
send_alert() {
local message="$1"
curl -s -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"${message}\"}" \
"${ALERT_WEBHOOK}" > /dev/null 2>&1
}
check_service() {
local service="$1"
if systemctl is-active --quiet "${service}"; then
return 0
else
return 1
fi
}
check_http() {
local url="$1"
local response
response=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "${url}")
if [ "${response}" = "200" ]; then
return 0
else
return 1
fi
}
# ========== 主逻辑 ==========
HOSTNAME=$(hostname)
HAS_ERROR=0
# 检查系统服务
for service in"${SERVICES[@]}"; do
if ! check_service "${service}"; then
log"ALERT: Service ${service} is not running!"
send_alert "[ALERT] Service ${service} is DOWN on ${HOSTNAME}"
HAS_ERROR=1
fi
done
# 检查HTTP端点
for endpoint in"${HTTP_ENDPOINTS[@]}"; do
if ! check_http "${endpoint}"; then
log"ALERT: HTTP endpoint ${endpoint} is not responding!"
send_alert "[ALERT] HTTP endpoint ${endpoint} is not responding on ${HOSTNAME}"
HAS_ERROR=1
fi
done
# 检查磁盘空间
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "${DISK_USAGE}" -gt 85 ]; then
log"WARNING: Disk usage is ${DISK_USAGE}%"
if [ "${DISK_USAGE}" -gt 95 ]; then
send_alert "[CRITICAL] Disk usage is ${DISK_USAGE}% on ${HOSTNAME}"
HAS_ERROR=1
fi
fi
# 检查内存使用
MEM_USAGE=$(free | grep Mem | awk '{print int($3/$2 * 100)}')
if [ "${MEM_USAGE}" -gt 90 ]; then
log"WARNING: Memory usage is ${MEM_USAGE}%"
send_alert "[WARNING] Memory usage is ${MEM_USAGE}% on ${HOSTNAME}"
fi
if [ ${HAS_ERROR} -eq 0 ]; then
log"OK: All health checks passed"
fi
exit${HAS_ERROR}
案例3:日志轮转和清理脚本
#!/bin/bash
# log_rotate.sh - 日志轮转和清理脚本
set -euo pipefail
# ========== 配置 ==========
LOG_DIRS=(
"/var/log/app"
"/var/log/nginx"
"/home/deploy/logs"
)
MAX_SIZE_MB=100
RETAIN_DAYS=30
COMPRESS_DAYS=1
# ========== 函数 ==========
log() {
echo"[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
rotate_large_logs() {
local dir="$1"
log"Checking for large logs in ${dir}..."
find "${dir}" -name "*.log" -size +${MAX_SIZE_MB}M 2>/dev/null | whileread -r logfile; do
local rotated="${logfile}.$(date +%Y%m%d_%H%M%S)"
log"Rotating ${logfile} ($(du -h "${logfile}" | cut -f1))"
mv "${logfile}""${rotated}"
gzip "${rotated}"
touch "${logfile}"
log"Created ${rotated}.gz"
done
}
compress_old_logs() {
local dir="$1"
log"Compressing logs older than ${COMPRESS_DAYS} days in ${dir}..."
find "${dir}" -name "*.log.*" ! -name "*.gz" -mtime +${COMPRESS_DAYS} 2>/dev/null | whileread -r logfile; do
log"Compressing ${logfile}"
gzip "${logfile}"
done
}
cleanup_old_logs() {
local dir="$1"
log"Cleaning up logs older than ${RETAIN_DAYS} days in ${dir}..."
local count
count=$(find "${dir}" -name "*.log.*.gz" -mtime +${RETAIN_DAYS} 2>/dev/null | wc -l)
if [ "${count}" -gt 0 ]; then
find "${dir}" -name "*.log.*.gz" -mtime +${RETAIN_DAYS} -delete 2>/dev/null
log"Deleted ${count} old log files"
fi
}
# ========== 主逻辑 ==========
log"Starting log rotation and cleanup..."
for dir in"${LOG_DIRS[@]}"; do
if [ -d "${dir}" ]; then
rotate_large_logs "${dir}"
compress_old_logs "${dir}"
cleanup_old_logs "${dir}"
else
log"WARNING: Directory ${dir} does not exist, skipping"
fi
done
log"Log rotation completed!"
四、最佳实践和注意事项
这一章是重头戏,我会详细讲述这些年踩过的8个大坑,以及如何避免它们。每一个坑都有血泪教训在里面。
坑1:环境变量问题(PATH不生效)
故障现场
2019年,我负责维护一个电商平台的后台服务。有一天凌晨,值班同事打电话说数据同步任务没跑。我登上服务器,手动执行脚本,一切正常。查了crontab配置,也没问题。到底怎么回事?
折腾了一个多小时,终于发现问题:脚本里用了aws命令来上传备份文件到S3,这个命令是通过pip安装的,路径在/usr/local/bin/aws。手动执行的时候,我的shell环境有完整的PATH,能找到这个命令。但crontab执行的时候,PATH是精简版的,只有/usr/bin:/bin,自然找不到。
问题根源
crontab执行任务时,环境和你在终端手动执行是完全不同的:
crontab不会加载~/.bashrc、~/.bash_profile这些文件,所以你在这些文件里配置的环境变量、别名、函数,crontab里统统用不了。
解决方案
方案一:在crontab开头定义PATH(推荐)
# 在crontab文件开头添加
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SHELL=/bin/bash
# 然后是你的定时任务
0 2 * * * /home/deploy/scripts/backup.sh
方案二:在脚本里使用绝对路径
#!/bin/bash
# 使用绝对路径调用命令
/usr/local/bin/aws s3 cp /data/backup.tar.gz s3://my-bucket/
方案三:在脚本开头source环境配置
#!/bin/bash
# 加载用户环境
source ~/.bashrc
# 或者
source /etc/profile
# 然后执行你的逻辑
aws s3 cp /data/backup.tar.gz s3://my-bucket/
方案四:在crontab里直接source
* * * * * source ~/.bashrc && /home/deploy/scripts/backup.sh
我的建议
我个人推荐方案一和方案二结合使用。在crontab开头定义PATH,同时在脚本里也使用绝对路径。这样双重保险,不会出问题。
另外,写完脚本后一定要用这种方式测试:
# 模拟crontab的环境来测试脚本
env -i PATH=/usr/bin:/bin HOME=$HOME SHELL=/bin/bash /bin/bash /path/to/your/script.sh
坑2:权限问题
故障现场
有一次我们的日志清理任务突然不工作了。脚本是普通用户跑的,但要清理的目录里有些文件是root创建的。之前一直没问题,后来运维同事加了一个日志收集agent,这个agent是root运行的,创建的日志文件自然也是root权限,我们的清理脚本就清理不掉了。
问题分类
权限问题主要有这几类:
脚本本身没有执行权限 脚本要访问的文件/目录没有权限 脚本要执行的命令需要sudo 用户被禁止使用crontab
解决方案
# 1. 确保脚本有执行权限
chmod +x /path/to/script.sh
# 2. 检查文件/目录权限
ls -la /path/to/data/
# 3. 如果需要root权限,使用root的crontab
sudo crontab -e
# 4. 或者配置sudo免密码(仅限特定命令)
# 编辑 /etc/sudoers.d/deploy
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx
给脚本配置sudo免密码
如果确实需要在普通用户的crontab里执行需要root权限的操作,可以这样配置:
# /etc/sudoers.d/cron-scripts
# 允许deploy用户无密码执行特定脚本
deploy ALL=(root) NOPASSWD: /home/deploy/scripts/log_cleanup.sh
# 或者允许执行特定命令
deploy ALL=(root) NOPASSWD: /usr/bin/find /var/log -delete
然后在crontab里:
0 3 * * * sudo /home/deploy/scripts/log_cleanup.sh
权限检查清单
每次配置crontab前,过一遍这个清单:
# 检查脚本权限
ls -la /path/to/script.sh
# 检查脚本涉及的目录权限
ls -la /path/to/source/
ls -la /path/to/dest/
# 检查脚本里调用的命令是否需要sudo
which some_command
ls -la $(which some_command)
# 模拟crontab用户执行
sudo -u deploy /path/to/script.sh
坑3:时区问题
故障现场
这个坑我踩过两次。第一次是在AWS上,服务器时区是UTC,而业务要求北京时间凌晨2点执行。我配置的是0 2 * * *,结果任务是北京时间上午10点才跑,整整差了8个小时。
第二次更狗血。我们有个跨国业务,服务器分布在多个区域。运维同事把配置同步到所有服务器,结果发现东京、新加坡、伦敦的服务器都是当地时间2点执行,而不是北京时间2点。数据同步乱成一团。
问题详解
crontab使用系统时区。不同的是:
crond服务读取的是启动时的时区设置 修改系统时区后,需要重启crond服务才能生效 Docker容器内的时区可能和宿主机不同
解决方案
方案一:确保系统时区正确
# 查看当前时区
timedatectl
# 设置时区为上海
sudo timedatectl set-timezone Asia/Shanghai
# 重启cron服务使时区生效
sudo systemctl restart crond # CentOS/RHEL
sudo systemctl restart cron # Ubuntu/Debian
方案二:在crontab里设置TZ变量
某些版本的cron支持在crontab里设置时区:
# 设置时区
TZ=Asia/Shanghai
# 下面的任务按上海时区执行
0 2 * * * /path/to/script.sh
但要注意,这个特性不是所有cron实现都支持。在使用前最好测试一下。
方案三:使用systemd timer(推荐)
systemd timer支持更灵活的时区设置:
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily backup timer
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
方案四:在脚本里处理时区
如果任务对时间敏感,可以在脚本开头加入时区检查:
#!/bin/bash
# 强制使用指定时区
export TZ=Asia/Shanghai
# 检查当前时间是否在预期范围内
CURRENT_HOUR=$(date +%H)
if [ "$CURRENT_HOUR" != "02" ]; then
echo"WARNING: Expected to run at 02:00, but current hour is ${CURRENT_HOUR}"
echo"This might indicate a timezone issue"
fi
# 继续执行任务
...
Docker环境的时区处理
在Docker容器里跑crontab,时区问题更容易出现:
# Dockerfile中设置时区
FROM ubuntu:22.04
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo$TZ > /etc/timezone
或者在docker-compose里:
services:
cron:
image:your-cron-image
environment:
-TZ=Asia/Shanghai
volumes:
-/etc/localtime:/etc/localtime:ro
坑4:邮件发送问题
故障现场
曾经有段时间,服务器的磁盘莫名其妙就满了。排查半天发现/var/spool/mail/目录占了好几个G。打开一看,全是crontab的输出邮件,每分钟执行的任务产生的输出都被存成邮件了。
问题详解
crontab有个"贴心"的设计:默认会把任务的stdout和stderr输出通过邮件发送给用户。如果服务器没有配置邮件服务,或者邮件发不出去,这些邮件就会堆积在本地。
解决方案
方案一:禁用邮件通知(推荐)
在crontab开头设置:
MAILTO=""
或者在每个任务后面丢弃输出:
* * * * * /path/to/script.sh > /dev/null 2>&1
方案二:正确重定向输出
不建议完全丢弃输出,最好重定向到日志文件:
# 标准输出和错误都写入日志
* * * * * /path/to/script.sh >> /var/log/cron/script.log 2>&1
# 或者分开记录
* * * * * /path/to/script.sh >> /var/log/cron/script.log 2>> /var/log/cron/script.error.log
方案三:发送到指定邮箱
如果确实需要邮件通知,配置正确的收件人:
MAILTO="admin@company.com"
# 或者多个收件人
MAILTO="admin@company.com,ops@company.com"
但这需要服务器正确配置邮件服务(如postfix、sendmail)。
我的最佳实践
# crontab文件开头
MAILTO=""
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# 每个任务都写入独立的日志文件
0 2 * * * /home/deploy/scripts/backup.sh >> /var/log/cron/backup.log 2>&1
*/5 * * * * /home/deploy/scripts/monitor.sh >> /var/log/cron/monitor.log 2>&1
然后配置logrotate来管理这些日志:
# /etc/logrotate.d/cron-logs
/var/log/cron/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 644 deploy deploy
}
坑5:脚本路径问题
故障现场
有个同事配置的crontab任务一直不执行。他的配置是这样的:
0 2 * * * backup.sh
他说"我已经把backup.sh放到/usr/local/bin了,手动执行没问题啊"。
问题在于,crontab执行时的工作目录是用户的HOME目录(或者在crontab里用HOME变量指定的目录),不是/usr/local/bin。虽然PATH里包含/usr/local/bin,能找到backup.sh这个命令,但如果脚本里用了相对路径引用其他文件,就会出问题。
问题分析
crontab的工作目录相关问题:
脚本的路径必须是绝对路径或者在PATH中 脚本内部使用的相对路径,是相对于工作目录,不是脚本所在目录 crontab执行时的工作目录默认是用户HOME
解决方案
方案一:始终使用绝对路径
# crontab里使用绝对路径
0 2 * * * /home/deploy/scripts/backup.sh
方案二:在脚本里切换到正确目录
#!/bin/bash
# 获取脚本所在目录并切换过去
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${SCRIPT_DIR}" || exit 1
# 现在可以安全使用相对路径了
source ./config.sh
./helper.sh
方案三:在crontab里先cd再执行
0 2 * * * cd /home/deploy/scripts && ./backup.sh >> /var/log/backup.log 2>&1
脚本路径最佳实践
我习惯在每个脚本开头加这段代码:
#!/bin/bash
set -euo pipefail
# 获取脚本真实路径(处理软链接情况)
SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
SCRIPT_DIR="$(dirname "${SCRIPT_PATH}")"
SCRIPT_NAME="$(basename "${SCRIPT_PATH}")"
# 切换到脚本目录
cd"${SCRIPT_DIR}" || exit 1
# 定义日志函数
log() {
echo"[$(date '+%Y-%m-%d %H:%M:%S')] [${SCRIPT_NAME}] $1"
}
log"Script started from ${SCRIPT_DIR}"
坑6:并发执行问题(flock锁)
故障现场
这个坑是真的痛。我们有个数据同步任务,配置的是每5分钟执行一次。正常情况下执行只需要1-2分钟。但有一天,源数据库响应变慢,同步任务执行时间超过了5分钟。
结果呢?每5分钟启动一个新的同步进程,但上一个还没执行完。越积越多,最后服务器直接OOM了。更惨的是,多个同步进程同时写同一份数据,数据全乱了,花了一整天才恢复。
问题分析
crontab不会检查上一次任务是否完成。到了设定的时间,它就会启动新任务,不管之前的还在不在跑。
这种情况特别容易发生在:
任务执行时间不稳定 依赖外部服务(网络、数据库) 处理的数据量会变化
解决方案:使用flock
flock是Linux自带的文件锁工具,可以保证同一时间只有一个实例在运行:
# 基本用法:-xn表示获取排他锁,获取不到就退出
* * * * * /usr/bin/flock -xn /tmp/myjob.lock /path/to/script.sh
# -w 指定等待超时时间(秒)
*/5 * * * * /usr/bin/flock -xn -w 10 /tmp/sync.lock /path/to/sync.sh
# 完整示例,包含日志
*/5 * * * * /usr/bin/flock -xn /tmp/sync.lock /home/deploy/scripts/sync.sh >> /var/log/sync.log 2>&1
flock参数说明
-x | |
-s | |
-n | |
-w N | |
-E N |
在脚本内部使用flock
有时候需要更精细的控制,可以在脚本内部使用flock:
#!/bin/bash
LOCK_FILE="/tmp/$(basename "$0").lock"
# 方式1:整个脚本加锁
exec 200>"${LOCK_FILE}"
if ! flock -xn 200; then
echo"Another instance is running, exiting..."
exit 1
fi
# 正常执行脚本逻辑
echo"Starting..."
sleep 60
echo"Done!"
# 锁会在脚本退出时自动释放
#!/bin/bash
LOCK_FILE="/tmp/$(basename "$0").lock"
# 方式2:使用子shell加锁
(
if ! flock -xn 200; then
echo"Another instance is running"
exit 1
fi
# 这里是需要加锁保护的代码
echo"Critical section"
sleep 30
) 200>"${LOCK_FILE}"
flock vs 其他方案
PID文件方式(不推荐,但有时候需要用)
#!/bin/bash
PID_FILE="/tmp/$(basename "$0").pid"
# 检查PID文件
if [ -f "${PID_FILE}" ]; then
OLD_PID=$(cat "${PID_FILE}")
# 检查进程是否真的在运行
ifkill -0 "${OLD_PID}" 2>/dev/null; then
echo"Process ${OLD_PID} is still running, exiting..."
exit 1
else
echo"Removing stale PID file..."
rm -f "${PID_FILE}"
fi
fi
# 写入当前PID
echo $$ > "${PID_FILE}"
# 确保退出时删除PID文件
trap"rm -f ${PID_FILE}" EXIT
# 执行主逻辑
...
坑7:日志输出问题
故障现场
有一次定时任务出了问题,我想看看日志排查原因。结果发现日志文件要么是空的,要么全是乱七八糟的内容混在一起。
原因是多方面的:
有些任务根本没有配置日志输出 多个任务写同一个日志文件,没有时间戳,没法区分 stdout和stderr没有区分处理
解决方案
规范的日志输出配置
# 每个任务独立日志文件,包含stdout和stderr
0 2 * * * /path/to/backup.sh >> /var/log/cron/backup.log 2>&1
# 错误单独记录
0 2 * * * /path/to/backup.sh >> /var/log/cron/backup.log 2>> /var/log/cron/backup.error.log
# 使用logger发送到syslog
*/5 * * * * /path/to/monitor.sh 2>&1 | /usr/bin/logger -t cron-monitor
脚本内规范化日志
#!/bin/bash
# 日志配置
LOG_DIR="/var/log/myapp"
LOG_FILE="${LOG_DIR}/$(basename "$0" .sh)_$(date +%Y%m%d).log"
# 确保日志目录存在
mkdir -p "${LOG_DIR}"
# 日志函数
log() {
local level="${1:-INFO}"
local message="${2:-}"
echo"[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] ${message}" | tee -a "${LOG_FILE}"
}
log_info() { log"INFO""$1"; }
log_warn() { log"WARN""$1"; }
log_error() { log"ERROR""$1"; }
# 重定向所有输出到日志
exec 1>>"${LOG_FILE}" 2>&1
# 使用示例
log_info "Script started"
log_info "Processing data..."
if some_command; then
log_info "Command succeeded"
else
log_error "Command failed with exit code $?"
fi
log_info "Script completed"
结构化日志(JSON格式)
对于需要用ELK等工具分析的日志,使用JSON格式更好:
#!/bin/bash
log_json() {
local level="$1"
local message="$2"
local timestamp=$(date -Iseconds)
local hostname=$(hostname)
local script_name=$(basename "$0")
printf'{"timestamp":"%s","level":"%s","host":"%s","script":"%s","message":"%s"}\n' \
"${timestamp}""${level}""${hostname}""${script_name}""${message}"
}
log_json "INFO""Script started"
log_json "ERROR""Something went wrong"
日志轮转配置
别忘了配置日志轮转,不然迟早撑爆磁盘:
# /etc/logrotate.d/cron-scripts
/var/log/cron/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 644 root root
dateext
dateformat -%Y%m%d
sharedscripts
postrotate
# 如果需要的话,在这里重载服务
endscript
}
坑8:特殊字符转义问题
故障现场
这个坑比较隐蔽。有一次我在crontab里配置了这样的任务:
*/5 * * * * /path/to/script.sh --config=/etc/app/config.json 2>&1 | grep -v "DEBUG"
结果任务一直不执行。后来发现,crontab里的%字符有特殊含义,需要转义。
问题分析
在crontab里,%是特殊字符,会被解释为换行。第一个%之后的内容会被当作标准输入传给命令。
比如这个配置:
0 2 * * * /path/to/script.sh %arg1%arg2
实际上等价于:
echo"arg1
arg2" | /path/to/script.sh
所以如果你的命令里有%,而且你不是故意这么用的,就会出问题。
解决方案
方案一:转义%字符
# 把 % 换成 \%
0 * * * * /path/to/script.sh --date=$(date +\%Y\%m\%d)
方案二:把复杂命令写到脚本里
与其在crontab里和特殊字符斗争,不如把逻辑写到脚本里:
# crontab
0 * * * * /path/to/wrapper.sh
# wrapper.sh
#!/bin/bash
/path/to/script.sh --date=$(date +%Y%m%d)
其他需要注意的特殊字符
% | \%转义 | |
# | ||
$ | \$ |
复杂命令示例
# 错误:% 没有转义
*/5 * * * * echo"Current time: $(date +%H:%M)" >> /var/log/time.log
# 正确:转义 %
*/5 * * * * echo"Current time: $(date +\%H:\%M)" >> /var/log/time.log
# 更好:写成脚本
*/5 * * * * /home/deploy/scripts/log_time.sh
五、故障排查和监控
5.1 日志查看
crontab的执行日志记录在系统日志中,不同发行版位置不同:
# CentOS/RHEL/Rocky Linux
sudo cat /var/log/cron
sudo tail -f /var/log/cron
# Ubuntu/Debian
sudo grep CRON /var/log/syslog
sudo tail -f /var/log/syslog | grep CRON
# 使用journalctl(systemd系统)
journalctl -u crond -f # CentOS
journalctl -u cron -f # Ubuntu
日志格式解读:
Jan 7 02:00:01 hostname CROND[12345]: (username) CMD (/path/to/script.sh)
时间戳 主机名 进程名和PID 执行用户 执行的命令
5.2 问题排查流程
当crontab任务不执行时,按照这个流程排查:
第一步:确认crond服务正在运行
systemctl status crond # CentOS
systemctl status cron # Ubuntu
# 如果没运行,启动它
sudo systemctl start crond
第二步:检查crontab配置语法
# 查看当前用户的crontab
crontab -l
# 检查是否有语法错误
# 可以用在线工具验证时间表达式:https://crontab.guru
第三步:检查系统日志
# 看看是否有执行记录
sudo grep "script.sh" /var/log/cron
# 如果没有执行记录,可能是时间表达式问题或服务问题
# 如果有执行记录但任务没正确完成,需要看任务自己的日志
第四步:手动执行测试
# 先以目标用户身份测试
sudo -u deploy /path/to/script.sh
# 模拟crontab环境测试
env -i PATH=/usr/bin:/bin HOME=/home/deploy SHELL=/bin/bash \
/bin/bash /path/to/script.sh
第五步:检查权限
# 脚本权限
ls -la /path/to/script.sh
# 涉及的目录权限
ls -la /path/to/data/
# 用户是否有使用crontab的权限
cat /etc/cron.allow
cat /etc/cron.deny
第六步:检查环境变量
# 在脚本开头添加调试信息
#!/bin/bash
env > /tmp/cron_env_debug.txt
echo"PATH: $PATH" >> /tmp/cron_env_debug.txt
echo"PWD: $PWD" >> /tmp/cron_env_debug.txt
echo"HOME: $HOME" >> /tmp/cron_env_debug.txt
5.3 常见问题速查表
systemctl status crond | ||
5.4 监控告警
对于重要的定时任务,必须建立监控。这里介绍几种常用的方法:
方法一:使用Healthchecks.io
Healthchecks.io是一个免费的定时任务监控服务。原理是给每个任务分配一个URL,任务执行完ping一下这个URL,如果超时没收到ping就告警。
#!/bin/bash
# 脚本开头
HEALTHCHECK_URL="https://hc-ping.com/your-uuid-here"
# 告知开始执行
curl -fsS -m 10 --retry 5 "${HEALTHCHECK_URL}/start" > /dev/null 2>&1
# 执行主逻辑
do_something
# 根据执行结果发送不同信号
if [ $? -eq 0 ]; then
curl -fsS -m 10 --retry 5 "${HEALTHCHECK_URL}" > /dev/null 2>&1
else
curl -fsS -m 10 --retry 5 "${HEALTHCHECK_URL}/fail" > /dev/null 2>&1
fi
方法二:使用Prometheus + Alertmanager
创建一个导出器脚本,记录每个任务的执行状态:
#!/bin/bash
# cron_exporter.sh - 记录cron任务执行状态
METRICS_FILE="/var/lib/prometheus/cron_metrics.prom"
JOB_NAME="$1"
START_TIME=$(date +%s)
# 执行实际任务
"${@:2}"
EXIT_CODE=$?
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
# 写入Prometheus格式的指标
cat >> "${METRICS_FILE}.tmp" << EOF
# HELP cron_job_exit_code Exit code of the last cron job run
# TYPE cron_job_exit_code gauge
cron_job_exit_code{job="${JOB_NAME}"} ${EXIT_CODE}
# HELP cron_job_duration_seconds Duration of the last cron job run
# TYPE cron_job_duration_seconds gauge
cron_job_duration_seconds{job="${JOB_NAME}"} ${DURATION}
# HELP cron_job_last_run_timestamp_seconds Timestamp of the last cron job run
# TYPE cron_job_last_run_timestamp_seconds gauge
cron_job_last_run_timestamp_seconds{job="${JOB_NAME}"} ${END_TIME}
EOF
mv "${METRICS_FILE}.tmp""${METRICS_FILE}"
exit${EXIT_CODE}
crontab配置:
*/5 * * * * /usr/local/bin/cron_exporter.sh backup /home/deploy/scripts/backup.sh
方法三:简单的告警脚本
如果没有专门的监控系统,可以用一个简单的脚本检查任务是否按时执行:
#!/bin/bash
# check_cron_jobs.sh - 检查关键cron任务是否按时执行
ALERT_WEBHOOK="https://hooks.slack.com/services/xxx"
check_last_run() {
local marker_file="$1"
local max_age_minutes="$2"
local job_name="$3"
if [ ! -f "${marker_file}" ]; then
send_alert "Cron job '${job_name}' marker file not found!"
return 1
fi
local file_age=$(( ($(date +%s) - $(stat -c %Y "${marker_file}")) / 60 ))
if [ ${file_age} -gt ${max_age_minutes} ]; then
send_alert "Cron job '${job_name}' last ran ${file_age} minutes ago (threshold: ${max_age_minutes})"
return 1
fi
return 0
}
send_alert() {
curl -s -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"[ALERT] $1\"}" \
"${ALERT_WEBHOOK}" > /dev/null
}
# 检查各个任务
check_last_run "/var/run/backup_completed" 1500 "Daily Backup"# 25小时
check_last_run "/var/run/sync_completed" 15 "Data Sync" # 15分钟
六、systemd timer vs crontab对比(2025年推荐)
说了这么多crontab的坑,你可能会问:有没有更好的选择?
答案是有的:systemd timer。
6.1 为什么要考虑systemd timer
systemd timer从2010年代中期开始就被引入主流Linux发行版,到了2025年,它已经相当成熟了。很多人还在用crontab,主要是因为习惯和惯性。但如果你开始一个新项目,我建议认真考虑systemd timer。
6.2 对比表
6.3 systemd timer使用示例
先创建一个服务单元文件:
# /etc/systemd/system/backup.service
[Unit]
Description=Daily Database Backup
After=network.target mysql.service
[Service]
Type=oneshot
User=deploy
ExecStart=/home/deploy/scripts/backup.sh
StandardOutput=journal
StandardError=journal
# 资源限制
MemoryMax=1G
CPUQuota=50%
# 安全沙箱
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=true
再创建对应的定时器文件:
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 2am
[Timer]
OnCalendar=*-*-* 02:00:00
# 如果错过了执行时间(比如机器关机),开机后会补执行
Persistent=true
# 随机延迟0-30分钟,避免所有机器同时执行
RandomizedDelaySec=1800
[Install]
WantedBy=timers.target
启用定时器:
# 重新加载配置
sudo systemctl daemon-reload
# 启用并启动定时器
sudo systemctl enable backup.timer
sudo systemctl start backup.timer
# 查看状态
sudo systemctl status backup.timer
sudo systemctl list-timers --all
6.4 时间表达式对比
crontab语法:
# 每天凌晨2点
0 2 * * * /path/to/script.sh
systemd OnCalendar语法:
# 每天凌晨2点
OnCalendar=*-*-* 02:00:00
# 工作日的上午9点
OnCalendar=Mon..Fri *-*-* 09:00:00
# 每周一和周四
OnCalendar=Mon,Thu *-*-* 00:00:00
# 每月1号和15号
OnCalendar=*-*-01,15 00:00:00
# 每5分钟
OnCalendar=*:0/5
你可以用systemd-analyze calendar命令验证时间表达式:
$ systemd-analyze calendar "*-*-* 02:00:00"
Original form: *-*-* 02:00:00
Normalized form: *-*-* 02:00:00
Next elapse: Wed 2025-01-08 02:00:00 CST
(in UTC): Tue 2025-01-07 18:00:00 UTC
From now: 17h left
6.5 我的2025年建议
新项目:优先使用systemd timer,特别是需要以下特性时:
秒级精度 失败重试 资源限制 依赖管理 更好的日志集成 已有项目:如果crontab用得好好的,没必要迁移。但遇到crontab解决不了的问题时,考虑systemd timer。
容器环境:如果是Kubernetes,用CronJob;如果是Docker,可以考虑supercronic或者直接用系统cron。
简单任务:如果就是一个简单的日志清理,crontab足够了,不用杀鸡用牛刀。
七、总结
7.1 要点回顾
这篇文章讲了8个crontab的常见坑:
环境变量:crontab执行环境和交互式shell不同,PATH可能不完整 权限问题:脚本权限、文件权限、用户权限都可能出问题 时区问题:一定要确认系统时区,修改后记得重启crond 邮件问题:设置MAILTO=""避免邮件堆积 路径问题:使用绝对路径,在脚本里正确处理工作目录 并发问题:使用flock防止任务重复执行 日志问题:规范化日志输出,配置logrotate 特殊字符:注意%号需要转义
以及一些最佳实践:
在crontab开头设置SHELL、PATH、MAILTO 每个任务都要有日志输出 重要任务使用flock加锁 建立监控告警机制 定期备份crontab配置 对于新项目,考虑使用systemd timer
7.2 进阶方向
如果你想深入学习定时任务调度,可以继续研究:
systemd timer:更现代的定时任务管理方式 Kubernetes CronJob:容器化环境下的定时任务 分布式调度系统: Airflow(数据工程常用) XXL-JOB(国内Java生态常用) Celery Beat(Python生态) Temporal(新一代工作流引擎) 可观测性:如何监控和追踪定时任务执行
7.3 参考资料
crontab(5) man page: man 5 crontabcrond(8) man page: man 8 crondsystemd.timer(5) man page: man 5 systemd.timercrontab.guru:https://crontab.guru Healthchecks.io:https://healthchecks.io
附录
A. 命令速查表
# crontab基本操作
crontab -l # 列出当前用户的定时任务
crontab -e # 编辑定时任务
crontab -r # 删除所有定时任务(危险!)
crontab -ri # 删除前确认
crontab file # 从文件导入
sudo crontab -u user -l # 查看其他用户的定时任务
# cron服务管理
systemctl status crond # CentOS/RHEL
systemctl status cron # Ubuntu/Debian
systemctl restart crond # 重启cron服务
# 日志查看
tail -f /var/log/cron # CentOS/RHEL
tail -f /var/log/syslog | grep CRON # Ubuntu/Debian
journalctl -u crond -f # systemd日志
# 文件锁
flock -xn /tmp/job.lock command# 排他锁,非阻塞
flock -x /tmp/job.lock command# 排他锁,阻塞等待
flock -xn -w 10 /tmp/job.lock command# 最多等10秒
# 环境测试
env -i PATH=/usr/bin:/bin bash script.sh # 模拟crontab环境
# 时区
timedatectl # 查看当前时区
timedatectl set-timezone Asia/Shanghai # 设置时区
# systemd timer
systemctl list-timers --all # 列出所有定时器
systemd-analyze calendar "时间表达式"# 验证时间表达式
B. 时间表达式速查
# crontab格式
* * * * * command # 每分钟
*/5 * * * * command # 每5分钟
0 * * * * command # 每小时整点
0 2 * * * command # 每天凌晨2点
0 2 * * 0 command # 每周日凌晨2点
0 2 1 * * command # 每月1号凌晨2点
0 2 1 1 * command # 每年1月1号凌晨2点
0 9-18 * * 1-5 command# 工作日9-18点每小时
0 2 * * 1,3,5 command # 周一三五凌晨2点
# 特殊字符串(部分cron实现支持)
@reboot command # 系统启动时执行
@yearly command # 等同于 0 0 1 1 *
@monthly command # 等同于 0 0 1 * *
@weekly command # 等同于 0 0 * * 0
@daily command # 等同于 0 0 * * *
@hourly command # 等同于 0 * * * *
C. crontab配置文件模板
# ============================================
# Crontab Configuration Template
# Author: Your Name
# Last Updated: YYYY-MM-DD
# ============================================
# 全局设置
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
HOME=/home/deploy
# ============================================
# 系统维护任务
# ============================================
# 日志清理(每天凌晨3点)
0 3 * * * /usr/bin/flock -xn /tmp/log_cleanup.lock /home/deploy/scripts/log_cleanup.sh >> /var/log/cron/log_cleanup.log 2>&1
# ============================================
# 数据备份任务
# ============================================
# 数据库备份(每天凌晨2点)
0 2 * * * /usr/bin/flock -xn /tmp/db_backup.lock /home/deploy/scripts/db_backup.sh >> /var/log/cron/db_backup.log 2>&1
# ============================================
# 监控检查任务
# ============================================
# 健康检查(每分钟)
* * * * * /home/deploy/scripts/health_check.sh >> /var/log/cron/health_check.log 2>&1
# ============================================
# 业务任务
# ============================================
# 添加你的业务任务...
D. 术语表
写完这篇文章,回想起这些年踩过的坑,真是感慨万千。希望这些经验能帮到正在读这篇文章的你。
如果你还有其他关于crontab的问题,欢迎讨论。定时任务看似简单,但要用好、用稳,还是需要花一些功夫的。
祝你的定时任务永远准时执行,半夜不用被电话叫醒!
(版权归原作者所有,侵删)
免责声明:本文内容来源于网络,所载内容仅供参考。转载仅为学习和交流之目的,如无意中侵犯您的合法权益,请及时联系Docker中文社区!

温馨提示:文章内容系作者个人观点,不代表Docker中文对观点赞同或支持。
版权声明:本文为转载文章,来源于 互联网 ,版权归原作者所有,欢迎分享本文,转载请保留出处!

发表评论