Crontab 从入门到自动化运维:让服务器自己打理自己
每天凌晨备份数据库、每周清理日志、每小时检查服务状态——这些重复性操作你还在手动做?Crontab 就是 Linux 上的定时任务调度器,学会它,80% 的运维工作自动完成。这篇文章从语法基础讲到生产级运维脚本,覆盖 12 个实用场景。
Crontab 语法 30 秒速通
* * * * * 要执行的命令
分 时 日 月 周
0-59 0-23 1-31 1-12 0-7
| 表达式 | 含义 |
|---|---|
0 3 * * * |
每天凌晨 3 点 |
*/5 * * * * |
每 5 分钟 |
0 0 * * 1 |
每周一凌晨 0 点 |
0 0 1 * * |
每月 1 号凌晨 0 点 |
0 9-18 * * * |
每天 9 点到 18 点,每小时整点 |
30 2 1,15 * * |
每月 1 号和 15 号凌晨 2:30 |
操作命令:
crontab -e # 编辑当前用户的定时任务
crontab -l # 列出当前用户的定时任务
crontab -r # 删除当前用户的定时任务(危险!)
场景一:数据库自动备份 + 过期清理
最常见需求。
# 每天凌晨 2 点备份 MySQL,保留最近 7 天
0 2 * * * /opt/scripts/backup-mysql.sh >> /var/log/backup.log 2>&1
backup-mysql.sh:
#!/bin/bash
BACKUP_DIR="/backup/mysql"
DB_USER="root"
DB_PASS="your_password"
RETENTION_DAYS=7
mkdir -p "$BACKUP_DIR"
# 备份所有数据库
mysqldump -u$DB_USER -p$DB_PASS --all-databases --single-transaction \
| gzip > "$BACKUP_DIR/full_$(date +%Y%m%d).sql.gz"
# 删除 7 天前的备份
find "$BACKUP_DIR" -name "full_*.sql.gz" -mtime +$RETENTION_DAYS -delete
echo "$(date): 备份完成,已清理 ${RETENTION_DAYS} 天前的旧备份。"
备份到本地不够,再加一条同步到对象存储:
# 凌晨 3 点同步到阿里云 OSS
0 3 * * * /opt/scripts/sync-to-oss.sh
场景二:SSL 证书到期前 30 天提醒
Let's Encrypt 证书 90 天过期,acme.sh 会自动续期。但万一自动续期失败呢?
# 每天上午 10 点检查证书到期时间
0 10 * * * /opt/scripts/check-ssl.sh
#!/bin/bash
DOMAIN="yourdomain.com"
DAYS=$(echo | openssl s_client -servername "$DOMAIN" -connect "$DOMAIN:443" 2>/dev/null \
| openssl x509 -noout -dates | grep notAfter | cut -d= -f2)
EXPIRY=$(date -d "$DAYS" +%s)
NOW=$(date +%s)
DIFF=$(( ($EXPIRY - $NOW) / 86400 ))
if [ $DIFF -lt 30 ]; then
# 发通知(替换成你的通知方式)
curl -s "https://your-webhook-url" -d "SSL 证书将在 ${DIFF} 天后过期!"
fi
场景三:服务健康检查 + 自动重启
# 每 5 分钟检查关键服务
*/5 * * * * /opt/scripts/health-check.sh
#!/bin/bash
SERVICES=("nginx" "mysql" "docker" "sshd")
for svc in "${SERVICES[@]}"; do
if ! systemctl is-active --quiet "$svc"; then
echo "$(date): $svc 已停止,尝试重启..."
systemctl restart "$svc"
# 重启后再次检查
sleep 5
if systemctl is-active --quiet "$svc"; then
echo "$(date): $svc 重启成功。"
else
echo "$(date): $svc 重启失败,需要人工介入!" | tee -a /var/log/health-critical.log
fi
fi
done
场景四:日志自动清理
日志不清理,3 个月后 /var 撑爆。
# 每天凌晨 4 点清理日志
0 4 * * * find /var/log -name "*.log" -mtime +30 -delete
0 4 * * * find /var/log -name "*.gz" -mtime +7 -delete
0 4 * * * journalctl --vacuum-time=7d
Docker 日志清理(更容易爆):
# 每周日凌晨清理 Docker 日志
0 5 * * 0 docker system prune -af --filter "until=168h"
场景五:磁盘空间告警
# 每小时检查磁盘使用率
0 * * * * /opt/scripts/disk-alert.sh
#!/bin/bash
THRESHOLD=85
USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
echo "警告:磁盘使用率 ${USAGE}%,已超过 ${THRESHOLD}% 阈值" \
| mail -s "磁盘告警: $(hostname)" [email protected]
fi
场景六:每周更新系统包
保持系统安全但不想每次手动 apt update:
# 每周日凌晨 3 点更新
0 3 * * 0 apt update && apt upgrade -y && apt autoremove -y
保险起见,可以加一个重启前自检:
0 3 * * 0 apt update && apt upgrade -y && apt autoremove -y && \
[ -f /var/run/reboot-required ] && shutdown -r +5 "系统更新需要重启"
/var/run/reboot-required 文件存在表示更新了内核之类需要重启的包,这时才重启。
场景七:Git 仓库自动同步
如果你的博客是静态站点(Hexo/Hugo)或用 Git 管理的配置:
# 每 30 分钟自动提交配置变更
*/30 * * * * cd /opt/nginx-config && \
git add -A && git commit -m "auto backup $(date)" && git push
场景八:定时从网站抓数据
# 每小时抓一次汇率
0 * * * * curl -s "https://api.exchangerate-api.com/v4/latest/USD" \
-o /data/exchange_rates/$(date +%Y%m%d_%H).json
配合数据分析脚本,自动生成报表。
场景九:DDNS 动态更新
家用宽带 IP 会变,用 crontab 定时更新 DNS:
# 每 10 分钟检查 IP,变了就更新 Cloudflare DNS
*/10 * * * * /opt/scripts/ddns-update.sh
#!/bin/bash
CURRENT_IP=$(curl -s ifconfig.me)
CACHED_IP=$(cat /tmp/last_ip.txt 2>/dev/null)
if [ "$CURRENT_IP" != "$CACHED_IP" ]; then
curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records/RECORD_ID" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"home.yourdomain.com\",\"content\":\"$CURRENT_IP\",\"ttl\":120}"
echo "$CURRENT_IP" > /tmp/last_ip.txt
echo "$(date): DDNS 更新为 $CURRENT_IP"
fi
场景十:VPS 流量统计
阿里云轻量服务器有流量限制,超了限速。
# 每天统计 vnstat 流量并记录
0 0 * * * vnstat -i eth0 --json m >> /var/log/traffic.json
月底汇总看有没有超限风险。
场景十一:浏览器 Cookie/Token 自动刷新
有些 API 的 token 需要定期刷新:
# 每 23 小时刷新一次 GitHub Token(GitHub token 24小时过期)
0 */23 * * * /opt/scripts/refresh-token.sh
场景十二:定时发日报到微信群
# 每天下午 6 点汇总服务器状态发消息
0 18 * * * /opt/scripts/daily-report.sh
#!/bin/bash
MSG="【服务器日报】\n"
MSG+="时间: $(date)\n"
MSG+="磁盘: $(df -h / | awk 'NR==2{print $5}')\n"
MSG+="内存: $(free -h | awk 'NR==2{printf "%.0f%%", $3/$2*100}')\n"
MSG+="负载: $(uptime | awk -F'load average:' '{print $2}')\n"
# 发到企业微信机器人
curl -s "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY" \
-H "Content-Type: application/json" \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$MSG\"}}"
Crontab 进阶技巧
日志重定向
# 标准输出和标准错误都追加到日志
0 3 * * * /opt/backup.sh >> /var/log/backup.log 2>&1
# 不想保留任何输出
0 3 * * * /opt/backup.sh > /dev/null 2>&1
环境变量问题
Crontab 的执行环境和交互式 shell 不同,PATH 很有限。脚本开头一定要写完整路径:
#!/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
或者在 crontab 顶部定义:
PATH=/usr/local/bin:/usr/bin:/bin
SHELL=/bin/bash
0 3 * * * /opt/backup.sh
锁机制:防止任务重叠
备份跑了 2 小时还没结束,下一次又来了。用 flock 或者自己写锁:
#!/bin/bash
LOCKFILE="/tmp/backup.lock"
if [ -f "$LOCKFILE" ]; then
echo "上次备份还在运行,跳过本次。"
exit 1
fi
touch "$LOCKFILE"
trap "rm -f $LOCKFILE" EXIT
# 真正的备份逻辑
trap EXIT 保证脚本无论正常结束还是异常退出,锁都会被清理。
常见问题
Q:crontab -e 和 /etc/crontab 有什么区别?
crontab -e 是当前用户的定时任务,/etc/crontab 是系统级。绝大多数场景用 crontab -e 就够了。需要指定执行用户时才用 /etc/crontab(格式多一个用户名字段)。
Q:怎么调试 crontab 任务?
- 先把任务改成
* * * * *(每分钟执行),确认能跑 - 看
/var/log/syslog里 crontab 的执行日志 - 在脚本里加详细 echo,重定向到文件
Q:时区问题?
Crontab 使用系统时区。确认时区:
timedatectl | grep "Time zone"
如果服务器在海外但你想按北京时间调度,先改时区:
timedatectl set-timezone Asia/Shanghai
12 个场景,12 个 Shell 脚本。全部扔进 crontab,你的服务器就从一个等待你下命令的工具,变成了一个自己管理自己的自治系统。