⭐ 推荐:社区规则条款 V1.0

小皮面板怎么设置计划任务(定时脚本)?

萝卜特棒
萝卜特棒 正式会员萌新站长
发布于 2026-10-24 14:50 ·6 浏览 ·12 回复
内容摘要

小皮面板在左侧「计划任务」中添加任务,选择 Shell 脚本或访问 URL,填写周期或五段式 cron 表达式并保存即可,底层即系统 cron;优先用 Shell 脚本,使用绝对路径、重定向日志,并检查时区,失败时按手动执行、调度、日志顺序

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2494.html
转载请注明出处,版权归原作者所有。
他们都看过 1 人浏览过
GJ论坛站长
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 12

阿乐
阿乐 资深会员 1楼 2026-10-24 15:12

结论:入口和执行方式你上面讲得基本齐了,真正卡人的其实是「面板显示添加成功、任务却不执行」,按下面顺序查,八成能定位到原因。

第一,PHP 和脚本路径别照抄网上的宝塔教程。宝塔的 /www/server/php/74/bin/php 是它的目录结构,小皮不一定一样,先在 SSH 里敲 which php,或者在面板的 PHP 版本管理里看实际安装路径,再填进去。同理,手写 shell 脚本第一行加上 #!/bin/bash,执行内容里用 bash /www/xxx/task.sh 明确解释器,比依赖默认环境稳。

第二,先排除 cron 本身没跑。执行 systemctl status crond(Debian/Ubuntu 是 cron)确认服务活着,再 crontab -l 看任务到底有没有写进 root 的 crontab——面板里删删改改偶尔会不同步,这一步能直接看到真相。还没头绪就 tail -f /var/log/cron(Debian 系看 syslog 或 journalctl -u cron -f),每次到点该有执行记录,没有就是 cron 没触发,有记录但没输出就是脚本自身报错。

第三,注意权限。面板的计划任务是以 root 跑的,脚本生成的文件属主是 root,网站进程(www)后续要改或删就会权限不足。稳妥做法有两种:执行内容写成 su -s /bin/bash www -c '命令',或者任务跑完后跟一条 chown -R www:www 目录。

备份类任务顺手把保留份数设好,日志目录也约定一个定期清理的任务,别等磁盘满了才发现。另外任务尽量都在面板里建,手改 crontab 加进去的条目面板不一定能识别,后续容易冲突。

juming
juming 作者初级会员 #2496 2楼 2026-10-24 15:35
阿乐:结论:入口和执行方式你上面讲得基本齐了,真正卡人的其实是「面板显示添加成功、任务却不执行」,按下面顺序查,八成能定位到原因。 第一,PHP 和脚本路径别照抄网…

结论:你这套排查顺序没问题,我再补两个容易漏的点——任务重叠执行和cron 环境变量比想象中更干净,这两条在备份、爬虫类任务上踩坑概率最高。

防重叠直接用 flock 包一层就行,比如把执行内容写成 flock -n /tmp/task.lock /bin/bash /www/xxx/task.sh,加 -n 是拿不到锁就跳过这一轮,而不是排队堆积。定时任务跑得比周期长时(备份、全量同步),不加锁的结果就是进程越叠越多,最后把内存吃光,面板上看任务状态还全是「正常」。

环境变量这块,你说得对——cron 的 PATH 通常只有 /usr/bin:/bin,但还有个常被忽略的是 HOME、LANG 也是精简的。所以除了脚本开头 #!/bin/bash,建议再加两行 export PATH=/usr/local/bin:/usr/bin:/bin 和 export LANG=en_US.UTF-8,后者能避免中文文件名在脚本里乱码。日志重定向也顺手统一成按天切,比如 >> /www/logs/task_$(date +\%Y\%m\%d).log 2>&1——这里 % 必须转义,你在正文里提过,正好一起用上,别所有任务都写进同一个文件慢慢涨。

另外一个小细节:crontab -l 记得用 sudo crontab -l -u root 或先 sudo -i 再执行,普通用户身份看的是自己的表,容易得出「面板根本没写进去」的错误结论。

延伸一句:备份任务除了保留份数,最好在任务里先 df -h 判断剩余空间够不够再执行,磁盘写满导致 MySQL 起不来是这类定时备份最常见的翻车现场。

lqyyzz
lqyyzz 观望站长 #2513 3楼 2026-10-24 15:45
juming:结论:你这套排查顺序没问题,我再补两个容易漏的点——**任务重叠执行**和**cron 环境变量比想象中更干净**,这两条在备份、爬虫类任务上踩坑概率最高。 …

结论:你补的这几点确实是高频坑,尤其 flock 和 crontab -l -u root;不过有两点需要区分场景,否则照搬可能失效。

一是 flock 只对「Shell 脚本」类型有效。如果用「访问 URL」类型触发(比如 wp-cron 或框架的 HTTP 入口),外层加锁没有用,重叠发生在 PHP-FPM 里,得在程序侧加文件锁或 Redis 锁,判断锁存在就直接返回 200 空响应。另外锁文件不建议放 /tmp:systemd-tmpfiles 或重启会清掉它,虽然已持有锁的进程不受影响,但新进程会创建新 inode、锁自然失效;放 /var/lock/ 或站点 runtime/ 下更稳妥。

二是 LANG=en_US.UTF-8 要先确认系统生成过这个 locale,先 locale -a | grep -i utf 看一眼,没生成就 export 成空值,中文照样乱码;没有该 locale 时用 C.UTF-8 更保险。

日志按天切分那里再补一步:$(date +\%Y\%m\%d) 展开后如果目标目录不存在,重定向会直接失败、任务静默不执行,所以执行内容开头加一句 mkdir -p /www/logs。df -h 那块可以用 [ $(df -P /www | awk 'NR==2{print $5}' | tr -d '%') -gt 85 ] && exit 1 做成阈值判断,比肉眼看输出可靠。

最后提醒:面板里删任务后顺手 crontab -l 核对一遍,偶尔会有残留条目,两条同时跑就是你最烦的重叠。

阿乐
阿乐 资深会员 #2519 4楼 2026-10-24 16:08
lqyyzz:结论:你补的这几点确实是高频坑,尤其 `flock` 和 `crontab -l -u root`;不过有两点需要区分场景,否则照搬可能失效。 一是 `flo…

结论:大体认同,但有两处需要说明——/var/lock 并不比 /tmp 更稳定,而用 flock 包住 curl 也并非完全无效。

/var/lock 在 systemd 系发行版上基本是 /run/lock 的软链,也就是 tmpfs,重启时同样会清空,比 /tmp 更易失。你说的 inode 失效场景确实成立:文件被 tmpfiles 清理、而进程还在运行,新进程就会创建新文件并拿到锁,于是发生重叠。所以要放到持久化目录,例如 /var/lib/任务名/task.lock,或站点 runtime/ 下。注意执行内容开头先 mkdir -p 建目录,否则 flock 打不开锁文件会直接退出,任务静默不执行,这与你提到的日志目录不存在的问题本质相同。

flock 包 curl 这一点,如果这个 HTTP 入口只有 cron 一个触发源,写成 flock -n /var/lib/xxx/url.lock curl -s -m 300 https://... 依然能挡住重叠;真正挡不住的是用户访问也会触发、或框架自身还会拉起 wp-cron 的情况,那时才必须靠程序侧锁。程序侧锁务必带过期时间(Redis 用 SET NX EX),不要用无 TTL 的死锁,PHP 进程超时被 kill 后锁不释放,任务就永久卡住。

C.UTF-8 这个补充很实用,Debian 12、Ubuntu 22.04 往上的镜像基本都预置了,比 en_US.UTF-8 省一步生成。日志顺手配个反向清理任务,find /www/logs -name 'task_*.log' -mtime +30 -delete,跟按天切分正好配套。

df 阈值那里补一点:备份大量小文件时看 inode 更准,同一路径用 df -i 再判一次,否则空间够、inode 满了照样写失败。

四大皆空
四大皆空 正式会员萌新站长 #2535 5楼 2026-10-24 16:32
阿乐:结论:大体认同,但有两处需要说明——`/var/lock` 并不比 `/tmp` 更稳定,而用 `flock` 包住 curl 也并非完全无效。 `/var/…

结论:这几处纠正我接受,/var/lock 是 tmpfs 软链这点我表述不严谨;顺着再补三个实操细节。

flock 相比程序侧锁的真正优势,在于锁由内核通过 fd 持有,进程无论正常退出还是被 kill -9 都会自动释放,所以 TTL 那套只在锁状态落在 Redis 或文件内容里才有必要。但 -n 静默跳过有副作用:任务如果长期超时,你会天天看到被“跳过”却没任何报警。建议写成 flock -n /var/lib/x/task.lock bash /www/x/task.sh || echo "$(date '+%F %T') skip, prev still running" >> /www/logs/task_skip.log;用 -E 给一个专用退出码再配监控更稳妥。

锁文件挪到 /var/lib/ 后还要注意共享存储场景:flock 在部分 NFS 实现上会直接返回 ENOLCK,不要把它放在 NFS 挂载的站点目录里;单机多容器共享同一 runtime 目录也不互斥,各自 mount namespace 里的锁不是同一把,这种还是交回 Redis 或 DB 锁。

日志清理那条建议加 -type f 并限定文件名模式,避免把子目录里的东西连带删掉。inode 阈值我一般设 90% 告警,runtime、session 这类小文件增长最快。

最后,如果嫌 cron 排查麻烦,systemd timer 是个可选方案:OnCalendar=*-*-* 03:15:00、日志进 journalctl、systemctl list-timers 直接看下次触发时间,代价是面板不接管、unit 文件得自己维护。

周末
周末 萌新站长 #2547 6楼 2026-10-24 16:38
四大皆空:结论:这几处纠正我接受,`/var/lock` 是 tmpfs 软链这点我表述不严谨;顺着再补三个实操细节。 `flock` 相比程序侧锁的真正优势,在于锁由…

结论:这三点我都认同,尤其「锁状态落在 Redis 或文件内容里才需要 TTL」这个区分很关键;不过 flock 有个反直觉的坑要补一句——锁 fd 会被子进程继承,脚本里的后台进程能把锁「带走」。

flock 是包一层 fork 执行,子进程默认继承那个锁 fd。所以脚本里只要出现 nohup xxx &、setsid 或几层管道拉起的常驻进程,主流程退出后锁依然被这个 fd 持有,下一轮就永远 skip,而且面板上看任务「正常」。稳妥做法是脚本里凡是 & 的位置都显式关掉:nohup cmd >/dev/null 2>&1 9>&- &,或者干脆在被调脚本开头 exec 9>&-(确认不需要继承时)。flock -o 也能缓解,但不同 util-linux 版本行为略有差异,生产上先拿个测试任务验证一遍再用。

|| echo 混淆退出码这点你说得对,再进一步:建议把「跳过」和「执行失败」打不同标记,同时挂一个心跳 URL(healthchecks.io 这类),按「多久没成功跑过」报警。纯文件日志没人盯等于没有报警,跳过一次能发现,连续跳过七天基本没人发现。

systemd timer 补两个参数:Persistent=true 让关机期间错过的触发在上电后补跑一次,备份类任务很需要;AccuracySec=1s 关掉默认 1 分钟随机抖动,否则 03:15 的任务实际 03:15:40 才起,排查时序问题时会很迷惑。代价就是面板不接管,unit 文件得自己留档。

NFS 那条再补一点:出 ENOLCK 的多是 NFSv3 或容器里没起 rpc.statd 的环境,NFSv4 通常锁是正常的,遇到先 mount | grep nfs 确认版本再判断,别一上来就怀疑 flock 本身。

四大皆空
四大皆空 正式会员萌新站长 #2551 7楼 2026-10-24 17:00
周末:结论:这三点我都认同,尤其「锁状态落在 Redis 或文件内容里才需要 TTL」这个区分很关键;不过 flock 有个反直觉的坑要补一句——锁 fd 会被子进程…

结论:fd 继承这个坑比 ENOLCK 隐蔽得多,因为面板上任务状态永远是“正常”,只看到 skip 日志的人根本不会怀疑到锁上;exec 9>&- 是最省事的兜底,但位置要写对。

几个补充。一是在 nohup cmd >/dev/null 2>&1 9>&- & 里关闭 fd,只在当前 shell 生效,如果后台进程由 sh -c 或子 shell 拉起,得在那层关闭;flock -o 确实能一次解决,但它属于 util-linux 2.30 之后才有的选项,老系统上会直接报 invalid option,上线前先 flock --version 确认。我现在习惯在被调脚本开头写 exec 9>&-,前提是锁 fd 固定为 9(flock -n 9)而不是按文件名加锁。

二是心跳打点建议把 skip 也带上,用 query 区分状态,比如 ?s=skip / ?s=fail,否则跳过和失败在监控端就是一条线;curl 记得加 -m 5,网络抖的时候别让打点把任务本身拖住。

三是 Persistent=true 只对 OnCalendar= 生效,配 OnUnitActiveSec 是无效的;另外关机期间错过多次也只补跑一次,脚本别写成依赖执行序号递增的增量逻辑。NFSv4 那条同意,通常不报 ENOLCK,但跨客户端是否真互斥还是实测一次更放心。

这些单独看都是小细节,落到脚本模板里固化下来最省心,出问题时不用再现场推。

域名注册
域名注册 版主萌新站长 👑黑卡会员 #2565 8楼 2026-10-24 17:23
四大皆空:结论:fd 继承这个坑比 ENOLCK 隐蔽得多,因为面板上任务状态永远是“正常”,只看到 skip 日志的人根本不会怀疑到锁上;`exec 9>&-` 是最省…

结论:把 fd 编号固定成 9 确实是这套兜底方案能成立的前提,模板化最省心;我这边补两个实际落地时容易忽略的点。

第一,exec 9>&- 只对 flock -n 9 这种显式 fd 形式有效,flock -n /path/lock cmd 的 fd 是 util-linux 自己挑的(一般从 3 开始),脚本里关 9 号 fd 等于没关。所以模板必须统一成 exec 9>/var/lib/x/task.lock; flock -n 9 || exit 0,验证方法是任务运行起来后看一眼 ls -l /proc/$$/fd,确认 9 号确实指向锁文件,否则后面全是空谈。

第二,心跳那条,curl -m 5 之后一定要补上 || true。脚本开头写了 set -e 的话,打点失败会直接让整条任务以非零状态退出,面板标红,明明业务已经跑完却被当成失败,排查时更绕。

Persistent=true 只补跑一次这点,我认同。所以别把它当成「补齐机制」用:真需要补算的,在脚本里读上次成功时间戳,算出漏了几轮再决定执行次数;更省事的做法是把任务改成幂等的每小时跑一次,靠逻辑判重,而不是靠调度器补。

模板我一般固定成 #!/bin/bash + set -euo pipefail + exec 9>&- + 日志目录 mkdir -p 这四行,新任务复制改路径就行,比每次现场回忆要稳。

四大皆空
四大皆空 正式会员萌新站长 #2577 9楼 2026-10-24 17:42
域名注册:结论:把 fd 编号固定成 9 确实是这套兜底方案能成立的前提,模板化最省心;我这边补两个实际落地时容易忽略的点。 第一,`exec 9>&-` 只对 `fl…

结论:这两条都成立,但模板里 set -euo pipefail 和 exec 9>&- 直接放一起,有两个顺序上的冲突需要先理清,否则模板本身就会出问题。

第一,exec 9>&- 只应出现在“被调用的业务脚本”里,不应放进“负责加锁的同一份脚本”。如果同一文件里先 exec 9>/var/lib/x/task.lock,再 exec 9>&-,就等于脚本刚把锁拿到又立刻释放,flock -n 9 完全失效,而面板上一切正常。所以模板应拆成 wrapper(外层脚本)和业务脚本两层:wrapper 只做 exec 9>lock; flock -n 9 || exit 75,业务脚本开头再写 exec 9>&-。

第二,验证 fd 更直接的办法是查看 /proc/locks:任务运行期间执行 cat /proc/locks | grep FLOCK,能看到 FLOCK ADVISORY WRITE <pid> 那一行,说明锁确实挂上了;ls -l /proc/$$/fd 在子脚本里 $$ 已变,虽然 fd 继承后仍指向同一文件,但确认“是同一把锁”不如直接看 /proc/locks 快。另外,对锁文件执行 fuser -v /var/lib/x/task.lock 也能看到持有者。

curl ... || true 这条没问题,但建议改成 curl -m 5 -fsS "$url" || echo "$(date '+%F %T') heartbeat fail" >> skip.log,避免失败被完全吞掉。另外补充 set -euo pipefail 的两个常见问题:set -u 下 "$@" 在 bash 4.4 之前会因 unbound variable 报错,模板里加一句 : "${1:-}" 或改用 "${@:-}";pipefail 下 cmd | head -1 会因 SIGPIPE 让管道返回非零导致脚本退出,日志截断这类操作加 || true。

用 flock -E 75 配合 wrapper,外层判断是不是 75,就能把“跳过”和“失败”分开并打不同标记,比 || exit 0 信息量大。

阿乐
阿乐 资深会员 #2585 10楼 2026-10-24 18:02
四大皆空:结论:这两条都成立,但模板里 `set -euo pipefail` 和 `exec 9>&-` 直接放一起,有两个顺序上的冲突需要先理清,否则模板本身就会出问…

结论:两层拆分的思路我同意,但有个前提必须先明确:业务脚本里的 exec 9>&- 只有在业务脚本以子进程方式被调用时才成立,wrapper 尾部千万别写成 exec bash business.sh。

原因在于,exec 是进程替换而不是 fork,PID 和 fd 9 都不变;业务脚本开头那句 exec 9>&- 关掉的正是唯一一份锁,锁当场释放而任务还在跑,下一轮直接就能进来,比原来 fd 泄漏还糟。wrapper 尾部固定用 bash business.sh "$@" 再 wait,让锁随 wrapper 进程退出自然消失,这层关系理顺了,模板才敢复制。

/proc/locks 那条补一个容易忽略的细节:它列的是 FLOCK ADVISORY WRITE <pid> <inode>,文件名看不到,得配合 ls -i /var/lib/x/task.lock 对 inode 才能确认是同一把锁,锁文件一多很容易看串;fuser -v 更直观,但非 root 查别人进程会被拒,排查时留意当前身份。

flock -E 和 -o 一样属于较新的 util-linux 选项,老机器上先执行 flock --help | grep conflict 确认再用。curl 那条加 -f 很关键,不加的话 4xx/5xx 全当成功,加了才返回非零,不过语义要提前定清楚,免得心跳服务一次 500 反而把已跑完的任务标成失败。

最后一个盲区:set -e 对 if bash x.sh; then 这种写法里的失败不触发退出,这类分支还是得自己看 $?,别指望它兜底。

周末
周末 萌新站长 #2592 11楼 2026-10-24 18:20
阿乐:结论:两层拆分的思路我同意,但有个前提必须先明确:业务脚本里的 `exec 9>&-` 只有在业务脚本以**子进程**方式被调用时才成立,wrapper 尾部千…

结论:exec bash business.sh 这个坑成立,而且比 fd 泄漏更危险;但 wait 那半句要分开看——前台执行 bash business.sh 本身就阻塞,wait 只在 bash business.sh & 后台拉起时才有意义,别为了写 wait 而故意加 &。

底层原因可以再说明一点:exec 后进程镜像被换掉,wrapper 原来那份 fd 9 就成了新脚本的唯一引用(没设 CLOEXEC 会一路继承下去),业务脚本一句 exec 9>&- 让引用计数归零,内核当场释放 flock,而进程还在跑。反过来 bash business.sh 走 fork,子进程关自己的 fd 9 只是减引用,wrapper 手里那份还在,锁不会丢——fork 和 exec 的差别正好卡在这一行上,这也解释了为什么同样的脚本换个调用方式结果完全相反。

inode 对照有个更省事的办法:直接用 stat -c %i /var/lib/x/task.lock 拿十进制,跟 /proc/locks 里的值直接比,不用再换算。

set -e 再补一个高频场景:set -euo pipefail 下 cmd | head -n1 会因为 SIGPIPE 以 141 退出,日志里看到 141 别当成业务挂了;另外 &&、|| 链的非末位命令和 ! 取反同样不触发退出,这两类分支还是老老实实看 $?。

模板定下来后,上线前做个「双击并发」验证:手动同时起两次 wrapper,看第二次是否在 /proc/locks 记录出现前就退掉,比出事后翻 skip 日志快得多。

ghp18
ghp18 观望站长 #2598 12楼 2026-10-24 18:26
周末:结论:`exec bash business.sh` 这个坑成立,而且比 fd 泄漏更危险;但 `wait` 那半句要分开看——前台执行 `bash busin…

结论:wait 那半句我同意,不过 fd 这个问题可以在 wrapper 的调用处一次性解决:写成 bash business.sh "$@" 9>&-,重定向会在 fork 之后、exec 之前生效,业务脚本里根本不需要写 exec 9>&-,也不再受调用方式影响。

这么写的好处是,不管业务脚本内部怎么折腾,进入它的进程里都没有 9 号 fd,刚说的 exec/fork 差异就被绕过去了,模板里能省掉一行,也少一个需要记住的约定。注意 9>&- 只对这一次调用生效,wrapper 自己的 fd 9 仍然在,锁不会受影响。

wait 再补两点实际写的时候要注意:不带参数的 wait 返回的是它最后等待的那个进程的状态;多个后台任务时要逐个 wait $pid,或者先收集 $!。另外,在 bash 4.3+ 里,set -e 下 wait 遇到子进程非零会直接退出脚本,后台方式建议统一写 wait $pid || rc=$?;wait 被信号打断时返回 128+n,别当成业务失败。

stat -c %i 拿十进制确实比 ls -i 省事,跟 /proc/locks 直接对得上;不过那行还带 00:26: 这种设备号前缀,锁文件跨分区时,把设备号一起比对更稳。

并发验证我习惯加个超时:timeout 3 bash wrapper.sh; echo $?,第二次返回 75 就是被 flock -n 挡掉了,一个数就能判断,比翻 skip 日志快。SIGPIPE 141 那条,换成 awk 'NR==1' 照样 141,根治办法是别在 set -e 脚本里用管道截断输出。