VPS脚本手动执行正常,Crontab却不运行?从PATH、工作目录、权限和日志查起

Cron不是你的交互式终端;先还原它真实使用的环境,再改任务配置
发布于
8

VPS脚本手动执行正常、Crontab却不运行,最常见原因不是Cron“失灵”,而是定时任务看到的用户、PATH、工作目录和Shell与交互式终端不同。先证明调度器是否触发,再检查脚本在哪一步退出;不要一上来重装Cron或给脚本开放过大权限。

先分清四种“没运行”

现象 实际可能发生了什么 先查什么
完全没有日志 Cron服务、时间表达式或用户配置有误 服务状态、系统日志、crontab归属
有启动记录但无结果 命令路径、工作目录或依赖失败 标准输出、标准错误、退出码
偶尔成功、偶尔跳过 任务重叠、锁、资源或网络依赖 进程、锁文件、持续时间
时间不符合预期 时区、夏令时或表达式理解不同 系统时区、Cron实现、调度记录

“没有生成目标文件”不等于任务从未启动。脚本可能已经运行,只是在第一条相对路径或依赖命令处失败。

第一步:确认Cron服务和任务归属

不同发行版的服务单元可能叫croncrond

systemctl status cron --no-pager
systemctl status crond --no-pager
crontab -l

只需使用系统实际存在的服务,不要同时启用两套调度器。还要确认任务写在谁的Crontab里:普通用户的crontab -e、root的Crontab、/etc/crontab/etc/cron.d/的字段格式并不完全相同。系统级文件通常还包含“运行用户”字段,照搬用户Crontab的一行可能导致列错位。

第二步:先让最小任务留下证据

在当前用户的Crontab中临时添加一个无敏感信息的诊断任务,例如把时间写入专用日志:

* * * * * /usr/bin/date -Is >> /tmp/cron-proof.log 2>&1

若最小任务也不触发,继续查服务、语法、用户和时间;若它正常,说明调度器基本可用,问题位于原命令或脚本环境。完成验证后删除临时任务和文件,避免长期制造无用日志。

第三步:Cron的PATH通常更短

终端能找到pythonnode或自定义命令,可能是因为登录脚本修改了PATH、加载了版本管理器或定义了别名。Cron通常不会读取这些交互配置。先查程序真实位置:

command -v python3
command -v node
command -v bash

在Crontab中使用明确路径,或在顶部设置经过核对的PATH:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SHELL=/bin/bash

不要把整个交互式环境无筛选地导入Cron,也不要把密钥、Token直接写进Crontab。敏感配置应由权限受控的文件或密钥管理机制提供。

第四步:相对路径会从意外目录开始

脚本中的./config.jsonlogs/app.log或相对导入,在终端里可能依赖当前目录。Cron启动时的工作目录不一定是脚本所在目录。可以在任务中明确切换:

cd /opt/myjob && /usr/bin/python3 /opt/myjob/job.py >> /var/log/myjob.log 2>&1

程序、配置、输入和输出路径尽量明确;日志目录必须预先存在,并由运行用户拥有正确权限。不要为了省事把目录设为全员可写。

第五步:检查Shebang、换行符与执行权限

head -n 1 /opt/myjob/run.sh
ls -l /opt/myjob/run.sh
file /opt/myjob/run.sh

Shell脚本应有正确的Shebang,例如#!/usr/bin/env bash或系统确认存在的解释器路径;从Windows上传的脚本可能带CRLF换行,导致解释器路径识别异常。可以显式调用解释器来区分“脚本不可执行”和“脚本内容失败”:

/bin/bash /opt/myjob/run.sh

运行用户还需要读取脚本、进入父目录、读取配置和写入输出的权限。用root运行不是通用修复,反而会扩大文件所有权和安全问题。

第六步:把标准输出、错误和退出码留下来

没有重定向时,Cron可能尝试通过本地邮件发送输出,而许多VPS没有配置邮件服务。诊断阶段应写入权限受控的日志:

/opt/myjob/run.sh >> /var/log/myjob.log 2>&1; echo "exit=$?" >> /var/log/myjob.log

同时查看系统日志,位置因发行版而异:

journalctl -u cron --since "-30 min" --no-pager
journalctl -u crond --since "-30 min" --no-pager

部分系统会把Cron记录写入/var/log/syslog/var/log/cron。日志中的“CMD”只证明命令被调度,不代表业务完成;还要看脚本日志和退出状态。常用日志阅读方法可参考极跃圈的Linux运维命令速查

第七步:时间、百分号和Shell语法是三个隐蔽点

时区

先运行timedatectl确认系统时区与同步状态。容器内、控制面板和应用可能采用另一时区,不能只看本地电脑时间。涉及跨时区业务时,日志中使用带偏移量的时间更容易核对。

百分号

在许多Cron实现中,未转义的%会被特殊处理。命令中使用date +%F等格式时,应按当前实现正确转义,或把复杂逻辑放进脚本,再由Cron调用脚本。

Shell语法

Crontab默认Shell未必支持交互终端中的Bash语法、别名和函数。复杂管道、条件和环境加载最好写入版本可控的脚本,显式指定解释器,并在与Cron相近的干净环境中测试。

第八步:任务重叠时使用单实例锁

任务每五分钟执行一次,不代表它一定在五分钟内结束。网络超时、数据库锁或数据量增长会让多个实例重叠,造成重复写入和资源争用。可使用flock或应用自身的租约机制:

*/5 * * * * /usr/bin/flock -n /run/lock/myjob.lock /opt/myjob/run.sh >> /var/log/myjob.log 2>&1

锁目录、权限和失败处理必须验证。任务被锁跳过时也应有可观察记录;涉及订单、通知或数据修改时,还应让业务操作具备幂等性。

什么时候改用systemd timer

需要明确依赖网络、失败重试、随机延迟、资源限制、持久化补跑或集中日志时,systemd timer往往比一行复杂Crontab更容易审计。但迁移前应停止旧调度,避免两套任务同时执行;先验证service单元,再启用timer,并保留回滚方式。

如果任务只在VPS重启后失效,还应结合极跃圈的VPS重启后的服务与依赖排查核对挂载、网络在线和启动顺序。

修复后的验收清单

  1. 调度日志能证明任务按预期用户和时间启动;
  2. 脚本日志包含开始、结束、退出状态和任务编号;
  3. 工作目录、解释器、配置和输出路径明确;
  4. 连续运行多个周期没有重叠或重复提交;
  5. 重启后任务仍存在,所需挂载与网络依赖已就绪;
  6. 日志有轮转策略,Crontab和输出中没有明文凭据。

结论

VPS Crontab不执行时,应先证明调度器是否触发,再依次检查用户、PATH、工作目录、解释器、权限、输出、时区和任务重叠。把复杂命令收进可测试的脚本,并让每次运行留下退出状态,通常比反复重启Cron更快找到根因。

常见问题(FAQ)

脚本手动执行正常,为什么Crontab不执行?
交互式终端和Cron的环境不同。Cron通常使用更精简的PATH、不同工作目录和非交互Shell,也可能以另一用户运行。脚本依赖相对路径、别名、虚拟环境或登录配置时就容易失败。
Crontab里的命令需要写绝对路径吗?
建议为程序、脚本、配置和输出文件使用明确路径,或在Crontab顶部设置受控PATH。绝对路径不能解决所有问题,但能减少工作目录和环境差异。
为什么Crontab里的百分号会让命令异常?
在许多Cron实现中,未转义的百分号具有特殊含义,可能把命令截断并将后续内容作为标准输入。日期格式等命令含百分号时需要按当前Cron实现正确转义。
Cron任务怎样避免重复运行?
任务执行时间可能超过调度间隔时,应使用flock、应用锁或等效单实例机制,并把跳过、超时和退出状态写入日志。不能仅靠缩短执行时间或盲目增加间隔。

本文由作者原创/授权发布于极跃圈(jiyueip.com)未经许可,禁止转载。题图来自Unsplash,基于CC0协议。

声明:极跃圈(JIYUEIP.com)内网友所发表的所有内容及言论仅代表其本人,并不反映任何极跃圈(JIYUEIP.com)之意见及观点。

0 讨论
热门最新
总结
暂无总结
0 / 600