PHP Server Monitor 3.5.2 常用 CLI 命令与 cron 托管详解

PHP Server Monitor 3.5.2 检测引擎 CLI 实测:cron/status.cron.php 全参数(-s/--status、--timeout、--uri)与防重入锁实测,容器内置循环、宿主 crontab、webcron 三种托管方式与 -s off 离线守护模式耗时实测。

PHP Server Monitor自托管监控PHP 应用2026/11/01

PHP Server Monitor 3.5.2 常用 CLI 命令与 cron 托管详解

目标:讲清 PHP Server Monitor 3.5.2 的命令行面——核心 CLI cron/status.cron.php 的全部参数逐条实测(-s/--status、--timeout=、--uri=),以及检测任务的四种托管方式(容器内置循环、宿主 crontab、webcron、手动执行)与防重入锁机制的实测行为。全部命令与回显已在真实服务器(Ubuntu 26.04.1 LTS,Docker Compose 部署的 PSM 3.5.2,见站内《PHP Server Monitor 3.5.2 Docker Compose 部署指南》)实测通过。

一、环境信息

  • PHP Server Monitor 3.5.2(官方 Dockerfile 自构建镜像,容器 phpservermon-3.5.2-web,PHP 7.4.33 CLI)
  • MariaDB 10.6.28,psm_config 表存放 cron 运行锁
  • 容器内 Web 根目录:/var/www/html(CLI 脚本位于 /var/www/html/cron/status.cron.php)
  • 宿主机 crontab 可用(/usr/bin/crontab,cron 守护进程运行中)
  • 实测时间:2026-11-01

二、工具概览

PSM 是传统 PHP Web 应用,没有管理类 CLI(不存在类似 php artisan server:add 的命令),命令行面聚焦在检测引擎上:

工具/形态位置用途是否逐条实测
php cron/status.cron.php容器内 /var/www/html/cron/检测引擎入口,跑一轮全部监控项✅
-s on / -s off同上参数常规检测 / 离线守护模式✅
--timeout=X同上参数单次覆盖锁超时✅
--uri=<url>同上参数CLI 下指定站点 URL✅
容器内置检测循环镜像 docker-entrypoint.sh每 PSM_REFRESH_RATE_SECONDS 秒自动跑一次 cron✅
宿主 crontab宿主机按 cron 计划调用容器内 CLI✅
webcronHTTP 触发 cron/status.cron.php外部调度器(如在线 cron 服务)经 HTTP 触发✅(403 拦截实测)
maria-wait.sh镜像内 /usr/local/bin/容器启动时等数据库就绪(内部脚本,非运维命令)—(随容器启动验证)

💡 说明:监控项/用户/通知渠道的管理都在 Web 界面(Servers、Users、Config 模块);批量操作可直接改库(表结构与键值见站内《PHP Server Monitor 3.5.2 配置参数详解》),数据库备份用 mariadb-dump(见部署指南 10.2)。

三、前置条件

  • 容器运行中:docker exec phpservermon-3.5.2-web php -v 能输出版本(实测 PHP 7.4.33);
  • 已完成安装向导(psm_config 表有 version 键),至少 1 个 active='yes' 的监控项;
  • 用宿主 crontab 托管时,宿主机能执行 docker 命令(本文以 root 操作)。

四、cron/status.cron.php 命令详解

4.1 脚本位置与运行形态

docker exec phpservermon-3.5.2-web php /var/www/html/cron/status.cron.php

每轮执行逻辑(源码 src/psm/Util/Server/UpdateManager.php 实证):查询 psm_servers 中 active='yes' 的监控项,逐个调用 StatusUpdater->update() 检测(website=curl / service=fsockopen / ping=系统 ping 命令),结果写回 psm_servers 并归档到 psm_servers_uptime/psm_servers_history,状态变化时按渠道发通知。

4.2 无参数运行(全量检测,实测)

$ docker exec phpservermon-3.5.2-web php /var/www/html/cron/status.cron.php
$ echo $?
0

实测要点:成功时完全静默(无任何输出),退出码 0。检测是否真的跑了,看数据库时间戳:

docker exec phpservermon-3.5.2-db mariadb -uphpservermonitor -pq1w2e3r4 phpservermonitor \
  -e "SELECT server_id,label,status,ROUND(rtime,3) AS rtime,last_check FROM psm_servers;"
server_id  label           type      status  rtime   last_check
1          SourceForge     website   off     0.563   2026-11-01 11:59:48
2          Gmail SMTP      service   off     10.016  2026-11-01 12:00:08
3          本地 MySQL 端口   service   on      0.000   2026-11-01 12:00:08
4          Quzuo 官网首页   website   on      0.097   2026-11-01 12:00:08

4.3 -s / --status:检测模式选择(实测)

CLI 参数解析(源码 cron/status.cron.php 的 getopt('s:', ['status:']) 实证)接受 -s 或 --status,取值映射表(源码 $possibleValues 原样):

传入值实际模式
on / 1 / upon——常规检测
off / 0 / downoff——离线守护模式(见六、)

实测 -s on(与无参数等价,显式声明常规模式):

$ docker exec phpservermon-3.5.2-web php /var/www/html/cron/status.cron.php -s on
$ echo $?
0

-s off 的行为完全不同(只查当前 offline 的项、驻留 59 秒轮询),展开见六、。

4.4 --timeout=X:单次覆盖锁超时(实测)

默认锁超时为 PSM_CRON_TIMEOUT = 600 秒(src/includes/psmconfig.inc.php)。--timeout=x 可以在单次运行时覆盖它。实测方法:先把锁置位(模拟"上一轮还在跑"),再分别用默认超时与 --timeout=1 触发:

# 置位锁(运行时状态,等价于"有 cron 正在跑")
$ docker exec phpservermon-3.5.2-db mariadb -uphpservermonitor -pq1w2e3r4 phpservermonitor \
    -e "UPDATE psm_config SET value='1' WHERE \`key\`='cron_running'; \
        UPDATE psm_config SET value=UNIX_TIMESTAMP() WHERE \`key\`='cron_running_time';"

# 默认 600 秒锁:立即拒绝
$ docker exec phpservermon-3.5.2-web php /var/www/html/cron/status.cron.php
Cron is already running. Exiting.$ echo $?
0

# 等 2 秒后用 --timeout=1:锁被视为过期,放行执行
$ sleep 2
$ docker exec phpservermon-3.5.2-web php /var/www/html/cron/status.cron.php --timeout=1
$ echo $?
0

💡 说明:两个易踩的细节都在实测里——①锁冲突的输出 Cron is already running. Exiting. 由 die() 直接打印,末尾没有换行(上面的 $ 是下一命令提示符紧贴);②即便被锁拒绝,退出码依然是 0,监控 cron 本身的调度系统(如 cron-mail)不会因退出码发现冲突,只能靠输出文本或改用日志兜底。

4.5 --uri=:CLI 场景指定站点 URL(实测)

$ docker exec phpservermon-3.5.2-web php /var/www/html/cron/status.cron.php --uri=http://127.0.0.1:8138
$ echo $?
0

作用:CLI 没有 HTTP 上下文,--uri= 相当于临时 define('PSM_BASE_URL', ...),保证检测过程中构造的站内链接(通知邮件里的"查看详情"等)正确。参数格式为等号连接(源码按 explode('=', ltrim($argv, '--')) 解析,--uri http://... 分写两个参数不生效)。

五、防重入锁机制

5.1 锁的存储与判定

锁存在 psm_config 表(实测值):

key                    value
cron_running           0
cron_running_time      1793505983
cron_off_running       0
cron_off_running_time  1793505777

判定逻辑(源码原样语义):cron_running == 1 且 当前时间 - cron_running_time < 锁超时 时拒绝执行;cron 正常结束会把 cron_running 复位为 0。若上一轮崩溃未复位,锁超时(默认 600 秒,可用 --timeout 覆盖)后自动放行。

5.2 完整闭环实测

置位锁            → cron 运行 → Cron is already running. Exiting.(拒绝,见 4.4)
复位锁 value='0'  → cron 运行 → 静默(EXIT=0,正常执行)

5.3 与容器内置循环碰撞的实测(宿主 crontab 触发)

容器入口脚本自带每 60 秒一轮的检测循环(见七、)。在宿主 crontab 里也加一条每分钟调用的任务,两路触发撞在同一时刻时,防重入锁让后到者退出——实测 crontab 触发后的日志文件内容(连撞两次,各自紧贴无换行):

Cron is already running. Exiting.Cron is already running. Exiting.

这正是锁机制的价值:多路触发不会导致重复检测与重复告警。实践建议:容器化部署用内置循环即可,不要再加宿主 crontab;源码部署才需要 crontab(示例见 7.2)。

5.4 -s off 有独立的锁

-s off 模式使用 cron_off_running/cron_off_running_time(conf 前缀 cron_off_,源码实证),与常规模式的 cron_running 互不干扰——两者可并行运行。

六、-s off:离线守护模式(实测)

-s off 不是"关闭检测",而是对已离线监控项的加密轮询守护:只查询 status='off' 的监控项(源码 SQL AND status = 'off' 实证),在脚本内驻留约 59 秒、每 CRON_DOWN_INTERVAL(默认 5)秒补一轮检测,让刚恢复的服务尽快被发现并告警。

实测(当时有 2 个离线项:SourceForge 403 秒拒、Gmail SMTP 10 秒超时):

$ time docker exec phpservermon-3.5.2-web php /var/www/html/cron/status.cron.php -s off
sh: 1: /usr/sbin/sendmail: not found

real    2m23.212s
user    0m0.015s
sys     0m0.008s
EXIT=0

耗时解读(与源码逻辑吻合):59 秒的"驻留窗口"按 5 秒节拍推进,但每个节拍的实际检测耗时由离线项的连接超时决定(本例每轮 ≈10.5 秒:Gmail 10s 超时 + SourceForge 0.5s),12 个节拍累计约 2 分钟以上,实测 2m23s。期间一轮检测发现某离线项状态变化,触发了邮件告警尝试(输出 sh: 1: /usr/sbin/sendmail: not found,容器无 MTA,见八、)——顺带证明 -s off 确实在持续复查离线项。监控项越多、超时越长,-s off 单轮越久,官方文档建议它与常规 cron 分开、用低频计划调用。

七、四种托管方式详解

7.1 容器内置循环(Docker 部署默认,实测)

官方镜像入口脚本 docker-entrypoint.sh 最后一行(原样):

exec "$@" & (while true; do sleep "${PSM_REFRESH_RATE_SECONDS}"; /usr/local/bin/php ${APACHE_DOCUMENT_ROOT}/cron/status.cron.php; done)

即容器启动后,后台常驻循环每 PSM_REFRESH_RATE_SECONDS 秒(本文实测设 60)执行一次检测。行为特征(实测):

  • 循环无输出,docker logs 里看不到每轮检测痕迹,验证靠 psm_servers.last_check 时间戳推进;
  • 与手动执行/其他触发撞车时被锁拒绝(Cron is already running. Exiting.,实测见 5.3);
  • 改间隔需 docker compose up -d 重建容器(restart 不重读环境变量,实测过 Recreated 流程)。

7.2 宿主 crontab(源码/二进制部署标准方式,实测)

官方文档 docs/install.rst 给出的两种典型计划(源码包内原文示例):

# 单任务:每 15 分钟一轮常规检测
*/15 * * * * root /usr/bin/php /var/www/html/phpservermon/cron/status.cron.php

# 双任务组合:常规检测 + 离线守护
*/5 * * * * root /usr/bin/php /var/www/html/phpservermon/cron/status.cron.php -s on
*  * * * * root /usr/bin/php /var/www/html/phpservermon/cron/status.cron.php -s off

本文在 Docker 环境实测宿主 crontab 托管(通过 docker exec 进入容器执行):

# 安装带标记的任务(便于事后清理)
(crontab -l; echo '* * * * * docker exec phpservermon-3.5.2-web php /var/www/html/cron/status.cron.php >> /tmp/psm-crontab-test.log 2>&1 # psm-doc-test') | crontab -

# 等待下一分钟触发后查看回显
cat /tmp/psm-crontab-test.log
Cron is already running. Exiting.Cron is already running. Exiting.

(与容器内置循环同时触发,后到者被锁拒绝,证明托管链路可用且锁生效。)

实测后清理并还原 crontab:

crontab -l | grep -v 'psm-doc-test' | crontab -

⚠️ 注意:crontab 里重定向到日志文件是刻意为之——cron 默认会把命令输出发邮件给本地用户,而 PSM cron 的锁冲突文本末尾无换行、成功时无输出,落日志更易核对。

7.3 webcron:HTTP 触发(实测 403 拦截)

cron/status.cron.php 同时可经 HTTP 访问(http://<站点>/cron/status.cron.php),供外部在线 cron 服务触发。安全门槛(源码 src/bootstrap.php + 脚本头部实证):来源 IP 必须在 PSM_CRON_ALLOW 白名单(默认空数组,serialize(array())),或携带正确的 ?webcron_key=(对应 config.php 的 PSM_WEBCRON_KEY,默认空)。

本文实测:默认配置下直接 HTTP 访问被拒:

$ curl -si http://127.0.0.1:8138/cron/status.cron.php
HTTP/1.0 403 Forbidden
Date: Sun, 01 Nov 2026 04:01:38 GMT
Server: Apache/2.4.54 (Debian)
X-Powered-By: PHP/7.4.33
Content-Length: 397
Connection: close
Content-Type: text/html; charset=UTF-8


        <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html>
            <head><title>403 Forbidden</title></head>
            <body>
                <h1>Forbidden</h1><p>IP address not allowed. See the 
                <a href="http://docs.phpservermonitor.org/en/latest/install.html#cronjob-over-web">documentation</a> 
                for more info.</p>
            </body>
        </html>

放行方式二选一:①config.php 设 define('PSM_WEBCRON_KEY', '<随机串>') 后用 .../cron/status.cron.php?webcron_key=<随机串> 访问(成功时输出 OK 后执行检测);②在 PSM_CRON_ALLOW 里写白名单 IP(默认该常量未在 config.php 定义,bootstrap.php 兜底为空数组——需自行添加 define,如 define('PSM_CRON_ALLOW', serialize(array('192.168.1.10'))))。

7.4 手动执行

即四、的所有命令:排障、验证、跑临时检测时直接 docker exec ...(或源码部署下 php cron/status.cron.php)。本文全部实测命令即手动形态。

八、注意事项

  • 频率设计:常规检测周期 ≥ 最慢单项超时 × 项数/并发能力;-s off 单轮可能远超 59 秒(见六、实测 128 秒),与常规 cron 叠加时注意锁冲突属正常现象;
  • 退出码恒 0:被锁拒绝也是 0,靠退出码判断成败不可靠,以输出文本/落库时间戳为准;
  • 通知依赖:检测触发告警邮件走 mail() 或 SMTP(容器内无 MTA 时邮件失败但检测不受影响,实测日志 sh: 1: /usr/sbin/sendmail: not found);
  • 运行用户:源码部署建议以 Web 同一用户(www-data)跑 cron,避免会话/日志文件属主混杂;Docker 形态在容器内执行天然一致;
  • webcron 谨慎放行:webcron_key 一旦泄露任何人都能触发检测(打满目标站点),建议配合 HTTPS 与随机长 key。

九、常见问题(FAQ)

Q1:手动跑 cron 没有任何输出,怎么确认它真的执行了?

查 psm_servers.last_check(4.2 实测命令)或 psm_servers_uptime 行数增长;成功时静默是设计行为。

Q2:Cron is already running. Exiting. 一直出现?

说明上一轮还没结束(检测多、超时长)或频率设得过密。排查:SELECT * FROM psm_config WHERE \key` LIKE 'cron%'看cron_running_time与当前时间的差值;确属卡死可复位cron_running='0'`(600 秒后锁也会自动过期)。

Q3:--timeout=0 能永久关锁吗?

可以——源码判定 $cron_timeout > 0 才生效,设 0 等于禁用单次锁检查(PSM_CRON_TIMEOUT 常量同理)。但禁用后多路触发会真的并发重复检测,不建议。

Q4:webcron 访问 403 但我已设置 PSM_WEBCRON_KEY?

检查三点:config.php 里 define 是否被 Docker 入口脚本重写覆盖(PSM_AUTO_CONFIGURE=true 每次启动会重生成);URL 参数名必须是 webcron_key(源码原样);key 比较为字符串相等,注意前后空格。

Q5:crontab 用 root 还是 www-data 跑?

Docker 场景宿主侧随意(进容器后以容器用户执行);源码部署用与 Web 相同的用户,否则 psm_log 等写入属主不一致。官方示例(7.2)以 root 运行,配合固定绝对路径 /usr/bin/php。

十、参考链接