
OpenClaw Gateway 跑在服务器上最让人揪心的不是报错而是突然不吭声——日志停在那里最后一行显示(no output)进程没退出端口也开着但所有消息都石沉大海。如果你也遇到过这种卡死这篇文章应该能帮你少走不少弯路。我从手动 kill 加重启开始到把 Gateway 交给 systemd 托管再到配置 Watchdog 定时健康检查最终实现了无人值守下的自动恢复整个过程踩了不少坑今天把完整的解决思路和配置原原本本整理出来。内容不算难适合有一定 Linux 基础、正在用 OpenClaw 或其他 Node.js 常驻服务的同学参考。1. 先把问题看清楚OpenClaw Gateway 的“(no output)”到底是什么1.1 现象描述一场无声的“假死”先说现象。我用journalctl -u openclaw-gateway -f实时盯日志的时候发现输出停在一行(no output)。这里“no output”不是字面意义的“没有日志”而是 journald 明确告诉你这个服务的标准输出和标准错误在最近一段时间里什么都没产生。与此同时执行ps aux | grep openclaw进程还在ss -tlnp | grep 3000端口也在监听。看起来一切正常可你发一条消息过去没有任何回复定时任务也不触发整个 Gateway 就像一个“表面在跑、实际罢工”的员工。这就是典型的假死状态比崩溃更头疼因为 systemd 默认只会处理“进程退出”的场景而假死时进程不退系统会认为服务一切正常。我一开始还以为是日志工具的问题重启了 journald甚至换过pm2 logs来看结果一样。后来才确认问题的根源不在日志采集而在 Gateway 进程本身的事件循环已经完全卡住或者底层连接已经断了但上层没有感知。1.2 为什么会在无人值守时卡死OpenClaw Gateway 本质上是一个长驻型的 Node.js 服务它需要同时维持多个外部连接消息平台的 WebSocket、HTTP 回调、可能还有跟 AI 模型 API 之间的长连接。这种架构下卡死通常不是单个原因造成的我梳理了四个最常踩的坑第一底层 WebSocket 静默断开。消息平台侧如果长时间没有心跳或者网络中间设备把空闲连接回收了客户端没有触发close事件连接就成了“僵尸连接”。上层代码等回调永远等不到。第二Promise 永久挂起。外部 API 请求超时时间设置不当或者请求发出了但响应迟迟不回来异步任务就永远 pending 在事件循环里。Node.js 是单线程这类未完成的任务多了新任务排不上队表象就是(no output)。第三未捕获的异常导致状态错乱。有些第三方库抛出的异常没有被兜底捕获进程没退出但内部的一些状态机已经跑偏了后续消息进来处理逻辑直接跳过或者报错。第四资源泄漏累积到临界点。句柄数、内存占用缓慢爬升到某个阈值后 GC 频繁触发事件循环响应能力急剧下降最终表现为卡死。你可能会问这些问题难道不能靠升级版本解决吗能但升级只能降低概率不能根除。只要 Gateway 在跑长连接就有再次假死的可能性。所以正确的思路是先用一套可靠的进程托管方案把服务“管起来”再配合定时健康检查把卡死的实例“捞回来”最后再反向去查根因。2. 临时止血手动重启的完整操作2.1 三步定位法确认进程是假死而不是崩溃在谈自动化之前先把手动操作流程讲清楚因为后面所有配置都需要你用这套方法验证效果。第一步查进程状态。拿到 PID 和启动时间ps aux | grep openclaw注意看两个信息PID 和进程的启动时间。如果进程已经跑了好几天而且STAT列是S或Sl说明进程还活着只是卡住了。第二步查端口状态。确认 Gateway 的服务端口是否还在监听ss -tlnp | grep 3000这里假设你的 OpenClaw Gateway 监听 3000 端口实际端口以你的配置为准。如果端口还在说明服务没有崩溃但也不代表服务可用——因为端口监听是内核层面的跟应用层是否卡死没有关系。第三步看最近一段时间的日志。这是判断假死最直接的手段journalctl -u openclaw-gateway --since 1 hour ago | tail -n 50如果最近的日志只有启动信息或者停在某一行一直没有新内容基本可以断定进程处于假死状态。到这里别犹豫直接进入下一步重启流程。2.2 安全重启与启动验证手动重启的原则是先温和、后暴力千万不要一上来就kill -9。先尝试优雅停止让进程有机会清理连接sudo systemctl stop openclaw-gateway如果你的服务还不是 systemd 托管的只是用nohup或者裸node命令启动的那就先用kill PID发送 SIGTERM等 10 秒左右看进程是否退出。如果进程还没退再kill -9 PID。这个等待很关键。Node.js 收到 SIGTERM 后会触发process.on(SIGTERM)之类的回调正常情况下可以优雅地关闭数据库连接、WebSocket 连接避免出现半开连接导致的数据不一致。我见过有人直接kill -9重启后消息重复处理、数据库压力陡增的情况多半就是没给进程善后的机会。停止后再启动sudo systemctl start openclaw-gateway启动后验证三件事进程状态、端口监听、日志输出。一条命令全搞定systemctl status openclaw-gateway ss -tlnp | grep 3000 journalctl -u openclaw-gateway -n 20 --no-pager确认日志出现了启动成功的提示端口恢复监听这时候再发一条测试消息看到正常回复手动重启流程就结束了。不过手动重启只能救急不能根治。如果卡死发生在凌晨三点你不可能每次都爬起来手动处理。所以下一步把 Gateway 彻底交给 systemd 托管是所有自动化的基础。3. 治本第一步用 systemd 接管 Gateway 生命周期3.1 创建 systemd unit 文件每个参数都别偷懒如果你的 OpenClaw Gateway 还在用nohup node gateway.js 这种方式跑我建议你现在就换成 systemd。原因很简单systemd 能管开机自启、崩溃重启、日志统一采集后续的 Watchdog 自动恢复也是基于它来实现的。创建服务文件sudo nano /etc/systemd/system/openclaw-gateway.service写入以下内容[Unit] DescriptionOpenClaw Gateway Service Afternetwork-online.target Wantsnetwork-online.target [Service] Typesimple Useropenclaw Groupopenclaw WorkingDirectory/opt/openclaw EnvironmentNODE_ENVproduction ExecStart/usr/bin/node /opt/openclaw/gateway.js Restarton-failure RestartSec10 TimeoutStopSec30 KillModemixed [Install] WantedBymulti-user.target这里有几个参数是实践总结下来的关键点逐个说明。Typesimple表示 systemd 认为ExecStart启动的进程就是主进程不需要等待额外的启动完成通知。对大多数 Node.js 服务来说这是最合适的类型。User和Group我建议单独建一个系统用户比如openclaw别用 root 跑服务。一方面安全另一方面日志和文件权限更容易控制。创建用户的命令sudo useradd --system --home /opt/openclaw --shell /usr/sbin/nologin openclawRestarton-failure的意思是进程非正常退出时自动重启正常退出exit code 0则不重启。这里要注意on-failure并不能解决假死问题因为假死时进程不会退出systemd 根本收不到退出信号。所以它只是基础防护真正的自动恢复要靠下一章的健康检查。RestartSec10是重启前的等待时间给系统一个缓冲避免崩溃后立即重启导致循环崩溃。这个值可以根据情况调整崩溃频繁的服务建议 15~30 秒减少日志刷屏。TimeoutStopSec30是systemctl stop时的等待时间。超过 30 秒进程还没退出systemd 才会发送 SIGKILL。如果你发现服务经常停止超时说明进程里可能有无法中断的长连接或定时器需要从代码层面处理。KillModemixed是容易被忽略但很重要的参数。它表示停止服务时先向主进程发送 SIGTERM等待TimeoutStopSec超时后再向整个 cgroup 内的所有进程发送 SIGKILL。如果你的服务还有子进程用这个参数能确保子进程也被清理干净不会留下孤儿进程。写完后重载 systemd 配置sudo systemctl daemon-reload3.2 Restart 策略为什么 on-failure 还不够先看一下 systemd 的 Restart 可选值和适用场景Restart 值触发条件适用场景no任何退出都不重启一次性任务on-success仅退出码为 0 时不重启不推荐用于常驻服务on-failure非正常退出非 0 退出码、信号终止、超时时重启常驻服务的默认选择on-abnormal仅信号终止、超时、看门狗触发时重启异常敏感型服务on-abort仅未被干净退出的信号终止时重启调试用always任何退出都重启包括正常退出需要强制保持存活的场景对 OpenClaw Gateway 这类服务on-failure已经够用因为正常退出比如你手动systemctl stop的时候你不希望它自动重启。但如果进程进入假死状态进程不会退出on-failure就完全失效了必须靠外部手段主动探测并干预。另外有个细节建议用StartLimitIntervalSec和StartLimitBurst来控制重启频率防止服务在几秒钟内反复重启把系统资源耗尽[Unit] StartLimitIntervalSec300 StartLimitBurst5这个配置表示 5 分钟内最多重启 5 次超过限制就不再做自动重启需要人工介入。这个限制配合健康检查脚本非常重要否则万一根因没解决服务会在无限循环重启中浪费资源最后把整个系统拖垮。3.3 启动、开机自启与验证配置完成后启动服务并设置开机自启sudo systemctl enable --now openclaw-gatewayenable --now是“设置开机自启 立即启动”的组合操作等价于先systemctl enable再systemctl start。然后检查状态sudo systemctl status openclaw-gateway正常情况下你会看到Active: active (running)并且日志里能看到启动输出。这时候再模拟一次崩溃测试验证 systemd 的自动重启是否生效sudo pkill -f node /opt/openclaw/gateway.js sleep 15 systemctl status openclaw-gateway等 15 秒左右再查看状态如果服务已经重新变成active (running),说明 Restart 策略生效了。这一步验证很关键后面所有自动化都要基于 systemd 已经能正常拉起服务这个前提。到这一步你已经解决了“进程崩溃后自动重启”的问题但还没解决“进程假死后无人发现”的问题。接下来进入正题Watchdog 自动恢复。4. 更进一步Watchdog 自动恢复机制4.1 Systemd 自带的 WatchdogSec 机制能用但不是万能的systemd 确实自带 watchdog 机制而且不是摆设。它的工作流程是这样的在 service 配置里写WatchdogSec30systemd 启动服务后会设置一个WATCHDOG_USEC环境变量应用需要周期性地调用sd_notify(WATCHDOG1)接口向 systemd 报平安。如果 systemd 在WatchdogSec指定的时间内没收到心跳就会认为服务失去响应执行预定的动作默认是 SIGABRT 终止进程然后触发 Restart 策略。协议上就是这么设计的非常优雅。但问题在于你的应用得支持sd_notify。OpenClaw Gateway 这类直接用 Node.js 编写、没有专门适配 systemd 的代码默认是不会主动发心跳的。你就算在 unit 里加了WatchdogSec它也不会生效甚至可能导致 systemd 误以为进程失联直接把服务给杀了。有两种办法可以让WatchdogSec真正生效第一种是改代码在 Node.js 里引入systemd模块每隔WATCHDOG_USEC/2时间调用一次sd_notify第二种是用一个包装脚本在应用旁边起一个辅助进程定时发心跳。但说实话这两种方案都增加了系统复杂度对大部分人来说有点过度设计。所以我的建议是别纠结于 systemd 原生的WatchdogSec直接用“外部健康检查脚本 systemd timer”的组合更灵活、更直观而且不依赖应用代码什么服务都能用。另外systemd 还有一个RuntimeWatchdogSec参数那是系统级的硬件看门狗如果设置了系统卡死到一定程度会触发整个机器重启。这个太过激进不适合单服务场景建议别碰。4.2 实战方案健康检查脚本探测 timer 定时恢复我的做法是写一个健康检查脚本每 1 分钟跑一次检查 Gateway 的三个核心指标进程是否存活、端口是否监听、最近是否有日志输出。任何一项异常就执行systemctl restart openclaw-gateway把卡死的实例拉回来。脚本内容如下#!/usr/bin/env bash SERVICE_NAMEopenclaw-gateway PORT3000 LOG_FRESHNESS_SECONDS120 # 检查1systemd 是否认为服务在运行 if ! systemctl is-active --quiet $SERVICE_NAME; then echo [healthcheck] Service $SERVICE_NAME is inactive, restarting... systemctl restart $SERVICE_NAME exit 0 fi # 检查2端口是否还在监听 if ! ss -tln | grep -q :$PORT ; then echo [healthcheck] Port $PORT is not listening, restarting... systemctl restart $SERVICE_NAME exit 0 fi # 检查3最近是否有日志输出可选按需启用 RECENT_LOGS$(journalctl -u $SERVICE_NAME --since $LOG_FRESHNESS_SECONDS seconds ago --no-pager | wc -l) if [ $RECENT_LOGS -eq 0 ]; then echo [healthcheck] No recent logs for $LOG_FRESHNESS_SECONDS seconds, restarting... systemctl restart $SERVICE_NAME fi针对前两个检查项多说一句systemctl is-active --quiet返回 0 表示服务 active非 0 表示非 active这种情况下 systemd 本身可能已经在尝试重启了但我们再主动触发一次可以缩短等待时间。端口检查依赖ss命令如果你的系统没有这个命令可以用netstat -tln替代。日志新鲜度这个检查项要谨慎。如果服务在高负载下正常处理请求但日志级别设置得比较高可能确实一段时间没有新输出这时候就会出现误判重启。我的做法是把阈值设长一点比如 120 秒甚至更长而且只在前面两个检查都通过的情况下才启用作为补充信号。如果你的服务特别安静建议直接注释掉这一段靠端口检查就足够了。脚本写好后给执行权限并放到标准路径sudo chmod x /usr/local/bin/openclaw-healthcheck.sh4.3 用 timer 定时执行避免 cron 的坑这里我推荐用 systemd timer 而不是 cron原因有两个第一timer 的日志和系统日志统一管理排查问题方便第二timer 支持OnUnitActiveSec相对时间可以按“上次执行时间”来定周期cron 只能写绝对时间比较死板。创建 timer 文件sudo nano /etc/systemd/system/openclaw-healthcheck.timer[Unit] DescriptionOpenClaw Gateway periodic health check [Timer] OnBootSec5min OnUnitActiveSec1min Unitopenclaw-healthcheck.service [Install] WantedBytimers.targetOnBootSec5min表示开机 5 分钟后开始第一次检查避免服务还没起来就误判OnUnitActiveSec1min表示每次检查后隔 1 分钟执行下一次。这两个参数可以根据你的实际需求调整如果服务比较重要可以改成 30 秒。再创建对应的 service 单元这个 service 是一次性任务sudo nano /etc/systemd/system/openclaw-healthcheck.service[Unit] DescriptionOpenClaw Gateway health check job [Service] Typeoneshot ExecStart/usr/local/bin/openclaw-healthcheck.sh注意这里的Typeoneshot表示这是一个执行完就退出的任务不是常驻服务。timer 到点后会自动唤醒它执行完脚本就结束不需要手动停。然后启用并启动 timersudo systemctl daemon-reload sudo systemctl enable --now openclaw-healthcheck.timer查看 timer 运行状态systemctl status openclaw-healthcheck.timer journalctl -u openclaw-healthcheck.service --since 10 minutes ago --no-pager如果你看到了[healthcheck] Port 3000 is not listening, restarting...这类输出说明检查脚本正常执行并且能逻辑上正确地触发了重启。到这一步你的 OpenClaw Gateway 已经从“裸奔”变成了“有人值守”而且是 24 小时自动值守的那种。5. 踩坑记录与问题排查速查表5.1 常见问题与解决方法表格这一路配置下来我踩了不少坑也帮朋友排查过类似环境的问题整理出一个速查表方便你直接对照现象可能原因处理方式服务启动失败状态显示 failedExecStart 路径错误、工作目录不存在、权限不足运行systemctl cat openclaw-gateway确认配置用ls -l检查 WorkDir 和二进制文件的所有者journalctl 里完全看不到日志User 配置错误导致无权写日志、服务没被 systemd 接管检查 unit 文件的 User 项确认启动命令是 systemd 拉起而不是手动 node 启动端口被占用启动不了上一个实例残留、或系统里已有其他服务占用ss -tlnp服务停止时特别慢经常 TimeoutStopSec进程里有无法中断的长连接或定时器适当调大TimeoutStopSec从代码层面给 SIGTERM 增加清理逻辑假死之后 systemd 一直不重启Restarton-failure 对“进程仍在但无响应”无效按第 4 节配置健康检查脚本 timer主动检测并重启健康检查脚本报 “journalctl: command not found”系统中 journalctl 不在 PATH 里脚本中使用绝对路径/usr/bin/journalctl或者去掉日志新鲜度检查timer 没到点不执行一天都没跑timer 服务没 enable或者 OnUnitActiveSec 理解错误执行systemctl list-timers --all查看 timer 状态确认启用的是 timer 而不是 service服务频繁重启系统负载飙升根因没解决RestartSec 太短调大RestartSec到 15~30 秒先抓日志定位根本原因再考虑调参数5.2 我的几条实操心得先说最重要的一条自动恢复只是兜底不是万能药。如果你配置好 watchdog 之后发现服务每天都会自动重启一次那一定要花时间去看日志找出真正的原因。我遇到过一次OpenClaw Gateway 每隔几个小时就静默一次最后查下来是消息平台的 WebSocket 连接没有做心跳重连在代码里补上重连逻辑后才彻底解决。自动恢复机制帮你降低了损失但根因还是要自己修。第二点是健康检查的粒度问题。我试过把检查间隔设成 30 秒结果服务启动慢的时候脚本经常在服务还没完全就绪就发出重启指令导致反复重启。后来把间隔改成 1 分钟并且把OnBootSec设成 5 分钟整体就稳定多了。这个参数没有标准答案取决于你的服务启动速度和重要程度宁可把检查周期放宽一点也不要因为误判制造新的事故。第三点如果你是在 Docker 或 Kubernetes 环境里跑 OpenClaw Gateway别套用这套 systemd 方案。容器里的 init 进程不是 systemd而且systemctl restart在容器里通常不可用。容器环境应该用 Docker 的restart: always策略再加上健康检查指令healthcheck字段来做类似的事。别把两套机制混淆了。第四点关于日志。如果服务长期没有日志输出你根本没法判断它是正常静默还是假死。建议在启动 OpenClaw Gateway 时加上合理的日志级别确保至少有心跳、连接状态、错误堆栈这类关键信息定期输出。有日志才能让健康检查脚本的“日志新鲜度”检查有意义也才能让后续排查有迹可循。最后分享一个排查小技巧当服务疑似假死时别急着重启先手动执行一次健康检查脚本看它输出什么这能帮你判断是进程问题、端口问题还是日志问题sudo bash -x /usr/local/bin/openclaw-healthcheck.sh-x参数会把脚本里每条命令的执行结果打印出来一眼就能看到是哪一步触发了重启。这个小技巧我在很多场景下都用得上不管是排查部署脚本还是定时任务比盲猜日志高效得多。