发布时间:2026-09-15 12:32 更新时间:2026-09-15 12:32 阅读量:0
很多站长第一次写定时任务,都会遇到一个尴尬场面:脚本在终端里手敲能跑,写进 crontab 之后就像没存在过一样,日志没有、报错没有、备份也没生成。这不是 crontab 坏了,而是它的运行环境和你在 SSH 里登录的交互式 shell 差别不小。这篇就把 crontab 的语法、环境变量缺失的成因,以及任务不生效时的排查顺序讲清楚,让定时备份、日志清理这类活儿真正跑起来。
crontab 的时间表达式由五个字段组成,从左到右分别是分、时、日、月、周,字段之间用空格分隔,后面接要执行的命令。取值范围:分钟 0-59,小时 0-23,日期 1-31,月份 1-12,星期 0-7(0 和 7 都表示周日)。星号表示“每一个”,逗号列举多个值,短横线表示区间,斜杠表示步长。
几个常见写法可以记一下:0 3 * * * 表示每天凌晨 3 点整执行一次;*/10 * * * * 表示每 10 分钟一次;0 2 * * 0 表示每周日 2 点。注意“日”和“周”字段如果都不是星号,部分实现里是“或”的关系,容易踩坑,稳妥做法是只保留其中一个条件,另一个写星号。
# 查看当前用户的定时任务列表
crontab -l
编辑当前用户的定时任务(保存后自动生效)
crontab -e
每 10 分钟跑一次脚本,并把输出追加到日志
*/10 * * * * /usr/bin/bash /opt/scripts/check.sh >> /var/log/check.log 2>&1
系统级任务还可以放在 /etc/crontab 和 /etc/cron.d/ 目录下,这类文件的格式比用户 crontab 多一个字段——执行用户,写错这一列任务同样不会执行。具体格式以发行版官方文档为准。
crontab 执行命令时用的不是你的登录 shell,而是一个极简环境。最典型的问题是 PATH 很短,通常只有 /usr/bin:/bin 这类基础路径。于是你写 python3 xxx.py 或 docker ps 时,系统找不到命令,任务静默失败。解决办法很简单:命令一律写绝对路径,或者先在脚本里把 PATH 补全。
除了 PATH,还有几个高频坑。一是工作目录,crontab 默认在家目录而不是脚本所在目录,脚本里如果用相对路径读配置,必然找不到文件。二是 shell 类型,crontab 默认可能用 /bin/sh,而你脚本里用了 bash 独有语法,需要用 /usr/bin/bash /path/to/script.sh 显式指定解释器。三是脚本没有可执行权限,chmod +x 之后才谈得上执行。
推荐的做法是把环境变量在脚本开头统一声明,让脚本无论被谁调用都自洽:
#!/usr/bin/bash
脚本内自补环境,避免 crontab 环境过简
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH
明确进入工作目录,防止相对路径读文件失败
cd /opt/apps/backup || exit 1
关键命令写绝对路径
/usr/bin/mysqldump -u backup_user -p"$DB_PASS" app_db > app_db_$(date +%F).sql
密码这类敏感信息不要直接写在 crontab 命令行里,因为 crontab -l 会明文显示。可以放进权限为 600 的独立配置文件,由脚本 source 读取,这也是很多备份脚本的常规做法。
任务不生效时,与其反复改时间表达式,不如按顺序验证。第一步确认 cron 服务在运行,第二步看系统日志里有没有执行记录,第三步手动模拟 cron 的极简环境跑一遍脚本。系统日志位置因发行版而异,Debian/Ubuntu 一般在 /var/log/syslog,CentOS/RHEL 系在 /var/log/cron,journald 环境下可以用 journalctl -u cron 查看,具体以实际环境为准。
# 确认 cron 服务状态
systemctl status cron # Debian/Ubuntu
systemctl status crond # CentOS/RHEL
查看最近的定时任务执行记录
grep CRON /var/log/syslog | tail -n 20
journalctl -u cron --since "1 hour ago"
用极简环境手动跑脚本,复现 crontab 现场
env -i /usr/bin/bash /opt/scripts/check.sh
还有一个常被忽略的点:任务其实执行了,只是没有输出也没有日志,看起来像没跑。cron 默认会把命令的标准输出和错误通过邮件发给本机用户,如果服务器没装邮件服务,这些信息就丢了。所以命令末尾统一加上 >> /var/log/xxx.log 2>&1 是很有必要的,出问题时能第一时间看到报错。如果确实想收邮件,可以在 crontab 顶部设置 MAILTO 指向一个真实可收信的地址。
另外,短周期任务要防止上一次没跑完就叠加下一次,可以用 flock 加锁,避免备份脚本互相覆盖:
# 用文件锁保证同一时刻只有一个实例在跑
*/30 * * * * /usr/bin/flock -n /tmp/backup.lock /usr/bin/bash /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
小结一下:写 crontab 时记住三件事——时间字段别写错、命令和文件路径尽量用绝对路径、输出一定重定向到日志。排查时按“服务是否在跑、日志有无记录、极简环境能否复现”三步走,绝大多数“定时任务不生效”都能定位到具体原因。下一步建议把常用脚本统一放到 /opt/scripts 并加上注释头,再配合日志轮转控制文件大小,运维会省心不少。
| 📑 | 📅 |
|---|---|
| MySQL慢查询日志开启与优化入门 | 2026-09-15 |
| Docker Compose 部署多容器应用实战:环境变量、数据卷与重启策略配置要点 | 2026-09-15 |
| Nginx 日志按天切割与过期清理:logrotate 实战 | 2026-09-15 |
| Let's Encrypt 证书自动续期失败排查:日志、80端口校验与 reload 钩子 | 2026-09-13 |
| Docker 磁盘占用过高清理实战:overlay2、容器日志与悬空镜像 | 2026-09-12 |
| Nginx 499 状态码排查实战:客户端断连与 upstream 超时的区别 | 2026-09-16 |
| 容器内存超限被 OOM Kill 排查:指标、cgroup 与堆参数对应 | 2026-09-16 |
| MySQL 主从延迟排查:Seconds_Behind_Master 忽高忽低怎么定位 | 2026-09-16 |
| rsync 增量同步与断点续传实战:exclude、--delete 与限速 | 2026-09-16 |
| Nginx 后端真实 IP 获取:X-Forwarded-For 与 real_ip 配置 | 2026-09-16 |