前言:为什么每个站长都该学会 crontab
VPS 跑起来之后,真正考验运维水平的不是"装环境",而是"自动化"。备份数据库、清理日志、推送监控告警、定时抓取数据、凌晨低峰期执行重任务……这些操作如果每次都靠人工登录执行,迟早会出漏子:忘了备份、磁盘被日志塞满、证书到期没续。Linux 自带的 crontab(cron table)就是解决这类问题的标准工具:按时间规则在后台周期性执行命令或脚本,无需人工干预。
本篇文章从字段格式讲起,覆盖常用命令、备份实战、环境变量坑、日志排查与进阶技巧,看完就能在自己的 VPS 上搭一套靠谱的定时任务体系。
一、认识 crontab:五字段格式详解
一条 cron 任务由"时间表达式 + 要执行的命令"组成,时间表达式共五个字段,顺序固定:分、时、日、月、星期。
分 时 日 月 星期 命令
* * * * * /usr/local/bin/backup.sh
字段取值范围:
字段 范围 特殊字符
分 0-59 * , - /
时 0-23 * , - /
日 1-31 * , - /
月 1-12 * , - /
星期 0-7(0 和 7 都代表周日) * , - /
常见写法和含义:
# 每天凌晨 2:30 执行备份
30 2 * * * /opt/scripts/backup.sh
# 每 5 分钟执行一次健康检查
*/5 * * * * /opt/scripts/health.sh
# 每天 8 点到 18 点之间,每 2 小时执行一次
0 8-18/2 * * * /opt/scripts/sync.sh
# 每月 1 号和 15 号的 3 点执行
0 3 1,15 * * /opt/scripts/bill.sh
# 每周一到周五的早上 9 点执行
0 9 * * 1-5 /opt/scripts/workday.sh
几个易错点:日与星期同时设置时,两者是"或"关系,即任一匹配就会执行;比如 0 0 1 * 1 表示"每月 1 号"或"每周一"都会触发,而不是两者同时满足。
二、crontab 常用命令
crontab 命令负责管理当前用户的计划任务,最常用的就三条:
# 编辑当前用户的 crontab(默认用 vi/vim/nano)
crontab -e
# 查看当前用户的全部任务
crontab -l
# 清空当前用户的所有任务
crontab -r
管理其他用户的任务需要 root 权限,用 -u 指定用户:
crontab -u www -e # 编辑 www 用户的任务
crontab -u www -l # 查看 www 用户的任务
技巧:备份现有任务,防止误删后无法恢复。
crontab -l > /root/crontab-backup-$(date +%F).txt
三、实战:定时备份网站文件与 MySQL 数据库
这是最典型也最有价值的应用场景。先写备份脚本,再把它挂进 crontab。
3.1 编写备份脚本
#!/bin/bash
# /opt/scripts/backup.sh 网站文件 + 数据库全量备份
BACKUP_DIR=/data/backups
DATE=$(date +%Y%m%d-%H%M%S)
KEEP_DAYS=7
# 1. 备份网站目录(排除缓存、日志等无用文件)
tar czf "$BACKUP_DIR/web-$DATE.tar.gz" -C /var/www \
--exclude=wp-content/cache --exclude=*.log /var/www/html
# 2. 备份 MySQL 数据库(mysqldump 全量)
mysqldump -uroot -p'你的密码' --single-transaction \
--routines --triggers --databases myblog > "$BACKUP_DIR/db-$DATE.sql"
gzip "$BACKUP_DIR/db-$DATE.sql"
# 3. 只保留最近 7 天的备份,自动清理旧文件
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +$KEEP_DAYS -delete
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$KEEP_DAYS -delete
echo "[$(date +%F' '%T)] backup done: $BACKUP_DIR"
脚本要可执行,并且最好先手动跑一遍确认无误:
chmod +x /opt/scripts/backup.sh
/opt/scripts/backup.sh
3.2 挂进 crontab
crontab -e
# 每天凌晨 3:00 执行备份,输出写入独立日志
0 3 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
这里把标准输出和错误输出都追加到 /var/log/backup.log,方便日后排查。如果脚本里有 echo,别忘记 >> 追加重定向,否则每次执行都会覆盖上一次日志。
记住:crontab 里尽量写脚本的绝对路径,并且脚本内部也全部用绝对路径,避免环境变量导致的"找不到命令"问题(下文第四节详述)。
四、环境变量与 PATH:cron 里最常见的坑
很多人发现"脚本手动执行正常,挂到 cron 里却报错",八成是环境变量问题。cron 执行任务时的环境极其精简,PATH 默认通常是 /usr/bin:/bin,你用 nvm 装的 node、装在 /usr/local/bin 下的工具,都可能找不到。
解决方案:在脚本头部显式声明环境变量,或者直接用命令的绝对路径。
#!/bin/bash
# 显式指定 PATH,覆盖 cron 的默认值
export PATH="/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/sbin:$PATH"
export LANG=en_US.UTF-8
也可以查一下命令到底装在哪:
which mysqldump
# 输出类似 /usr/bin/mysqldump,直接写成绝对路径即可
另外一个经典坑:cron 里写 % 会被当作换行符处理,如果要传带百分号的参数(比如 date +%F),必须转义成 \%。稳妥的做法是把复杂逻辑全部写进脚本,crontab 只保留一行调用。
五、系统级任务:/etc/crontab 与 /etc/cron.d
除了用户级 crontab,Linux 还提供系统级定时任务配置文件。Debian/Ubuntu 系默认就有 /etc/crontab,它与用户 crontab 的格式不同:多了"执行用户"一列。
# /etc/crontab 示例(注意第六列是指定运行任务的用户)
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
/etc/cron.d/ 目录里的文件格式与 /etc/crontab 一致,适合软件包安装自己的定时任务(例如 /etc/cron.d/certbot)。/etc/cron.hourly、/etc/cron.daily 等目录则放可直接执行的脚本,系统会按规则统一调度。
写进 /etc/cron.d/ 的任务需要带用户名:
# /etc/cron.d/backup-jobs
30 2 * * * root /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
注意:cron.d 下的文件对权限敏感,属主必须是 root,且不能有组/其他用户的写权限,否则 cron 会静默忽略该文件。
六、特殊字符串:@reboot、@daily 等
除了五字段表达式,cron 还支持几个便捷的"别名":
@reboot # 开机时执行一次(适合启动定时拉取、预热等)
@daily # 每天 0 点执行,等价于 0 0 * * *
@hourly # 每小时执行,等价于 0 * * * *
@weekly # 每周日 0 点
@monthly # 每月 1 号 0 点
@yearly # 每年 1 月 1 号 0 点
实用例子:开机后自动启动 frp 内网穿透客户端。
@reboot /usr/local/bin/frpc -c /etc/frpc.toml >> /var/log/frpc.log 2>&1
提示:如果机器会不定期重启,配合 systemd 开机自启也许更合适;但 @reboot 在"脚本型任务"场景下仍然简单高效。
七、日志与排错:cron 没执行怎么查
定时任务"该跑没跑"是最高频的故障,按下面顺序排查基本能定位。
7.1 确认 cron 服务在运行
systemctl status cron # Debian/Ubuntu
systemctl status crond # CentOS/RHEL
# 未运行则启动并设置开机自启
systemctl enable --now cron
7.2 查看 cron 执行日志
# Debian/Ubuntu 默认记在 syslog
grep CRON /var/log/syslog | tail -50
# CentOS/RHEL 有独立日志文件
tail -50 /var/log/cron
日志里能看到每次任务的触发时间、执行用户和命令,例如:
Oct 27 03:00:01 vps CRON[12345]: (root) CMD (/opt/scripts/backup.sh >> /var/log/backup.log 2>&1)
7.3 检查脚本输出与退出码
# 手动执行并查看退出码,非 0 即为失败
/opt/scripts/backup.sh
echo "exit code: $?"
# 查看任务输出日志最后 50 行
tail -50 /var/log/backup.log
7.4 常见问题清单
症状 大概率原因
脚本手动执行正常、cron 不执行 PATH/环境变量缺失,或脚本不可执行(缺 +x)
任务执行了但没效果 脚本内部用了相对路径,cwd 与预期不符
日志里看不到任何记录 cron 服务未启动,或任务语法错误被静默忽略
每天执行的时间不对 服务器时区不是 Asia/Shanghai,timedatectl 查看
同一时刻任务重复执行 !!!注意这是非预期的重复执行
排查时区问题:
timedatectl set-timezone Asia/Shanghai
date
八、进阶技巧:防止重复执行、随机延迟与日志轮转
8.1 用锁文件防止脚本重叠执行
如果任务耗时可能超过执行间隔(例如每 5 分钟跑一次,但脚本要跑 10 分钟),会出现多个实例同时运行。用 flock 加锁:
# crontab 中通过 flock 保证同一时刻只有一个实例
*/5 * * * * /usr/bin/flock -xn /tmp/backup.lock /opt/scripts/backup.sh
锁被占用时该次任务直接跳过,不会排队堆积。
8.2 随机延迟,错峰执行
多个 VPS 或本机多任务都在整点执行会给上游接口造成压力,可以用 sleep 打散:
# 每次执行前随机等待 0-300 秒
30 2 * * * sleep $((RANDOM % 300)) && /opt/scripts/sync.sh
8.3 防止日志无限膨胀
重定向输出到固定日志文件后,文件会越涨越大,务必让 logrotate 接管(配置参考):
# /etc/logrotate.d/backup
/var/log/backup.log {
daily
rotate 14
compress
missingok
notifempty
copytruncate
}
copytruncate 用于不中断正在写的文件;配置完后执行 logrotate -f /etc/logrotate.d/backup 验证。
8.4 邮件通知(可选)
cron 默认会把任务输出通过邮件发给用户,VPS 上一般没有 MTA 会静默丢弃。如果想让失败时收到通知,在脚本里判断退出码并调用 Webhook/钉钉机器人的接口即可:
if [ $? -ne 0 ]; then
curl -s -X POST "https://your-webhook-url" \
-H "Content-Type: application/json" \
-d '{"text":"backup failed at '"$(date)"'"}'
fi
九、总结
crontab 是 Linux 自动化运维的地基:五字段格式记牢(分 时 日 月 星期)、命令用绝对路径、脚本显式声明 PATH、日志重定向到独立文件、定期用 logrotate 清理。按照"先手动跑通脚本 → 再挂 crontab → 最后看日志确认"的流程,基本能避开绝大多数坑。
推荐的排错口诀:一查服务、二看日志、三查权限、四看时区。把这篇文章里的配置模板抄下来改造,就能轻松实现数据库定时备份、日志自动清理、监控告警推送等常用自动化场景,让 VPS 真正"自己会干活"。




