SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-22

n8n VPS 持续离线?排查4种常见故障

n8n 显示“连接已断开”不一定是真离线。本文区分 WebSocket 代理故障、容器重启循环、内存不足终止和计划任务未触发,并提供对应检查方法。

n8n 为何持续离线:4 种故障,1 个现象

“n8n 持续离线”这一句话可能涵盖 4 种不同故障,每种故障都需要不同的修复方法。编辑器显示连接丢失提示,但容器运行正常。容器自行重启。内核因 Node.js 进程占用过多内存而将其终止。或者进程本身没有任何问题,只是已启用的工作流始终未触发。如果修改了错误的设置,您可能会花整个周末排查一个并不存在的问题。

因此,在修改任何配置前,先确定具体是哪一种故障。n8n 以单个 Node.js 进程运行,通常位于一个 Docker 容器中,并通过负责终止 TLS(传输层安全)的反向代理对外提供服务。每一层都有各自的故障方式,而浏览器会对所有这些故障显示相同的消息。

按以下顺序诊断

在 VPS(虚拟专用服务器)上运行这些命令,并读取本机输出的值。不要将这些值与论坛帖子中的数字比较。这里需要关注的值描述的是您的服务器,而不是其他人的服务器。

docker ps -a --filter name=n8n
docker logs --tail 200 --timestamps n8n
docker inspect n8n | grep -iE 'Status|Running|RestartCount|OOMKilled|ExitCode'
docker stats --no-stream

STATUS 列来自 docker ps -a,表示容器处于当前状态的时长。将该时长与问题开始的时间比较。如果容器在横幅出现前很久就已启动,说明 n8n 从未离线。出问题的是浏览器与后端之间的连接,也就是下一节介绍的 websocket 路径。

RestartCount 表示 Docker 重启此容器的次数。记下这个数字,等待 1 分钟,然后再次读取。如果您观察期间数字持续增加,说明容器处于重启循环中。每次重启前的日志行会记录原因。

OOMKilled 是一个 true 或 false 标志。True 表示 Linux 内核终止了进程,因为进程超出了内存限制。该限制可能来自容器本身,也可能来自整台机器。这个字段可以区分内存终止与其他类型的退出,因此应先读取它,再进行推测。

ExitCode 表示容器上次退出时返回的值。您不需要记住每个退出码的含义。先读取本机的值,再读取同一时间点的 docker logs 末尾内容。日志末尾和内存不足标志需要结合判断发生了什么;单独依赖其中任何一个都可能得出错误结论。

docker stats 会同时显示实时内存使用量和当前生效的内存限制。在第二个终端中保持该命令运行,触发导致故障的工作流,并观察故障发生时数字如何变化。


“连接已断开”提示通常由反向代理导致

n8n 编辑器会与后端保持一个长期打开的推送连接,以便将执行进度实时显示在画布上。默认情况下,该连接使用 WebSocket,这由 N8N_PUSH_BACKEND 选择,其默认值为 websocket。WebSocket 首先是一个普通的 HTTP 请求,并携带 Connection: Upgrade 和 Upgrade: websocket 标头。服务器返回 101 Switching Protocols,此后双方通过同一个 TCP 套接字进行双向通信。

有两种情况会导致连接中断,而且问题都出在代理上,而不是 n8n 上。代理向上游使用 HTTP/1.0,或删除了升级标头,导致升级从未完成,编辑器因此不断重新连接。另一种情况是升级成功,但代理随后因连接处于静默状态而关闭套接字,因为没有消息的 WebSocket 看起来与空闲连接完全相同。在这两种情况下,容器都处于正常状态。该提示表示浏览器失去了与后端的通信通道。

在修改任何配置前,先在浏览器中确认这一点。打开开发者工具,进入 Network 选项卡,将筛选条件设为 WS,然后重新加载编辑器。推送请求应到达 101 Switching Protocols 并保持打开状态。如果推送请求返回普通状态码,或每隔几秒重新出现一次,则问题很可能出在代理上。

保持编辑器连接的 nginx 设置

nginx 不会自动转发升级请求,除非明确配置。proxy_pass 默认使用 HTTP/1.0 与后端通信,而 Connection 和 Upgrade 属于逐跳标头,nginx 转发时会将其删除。必须将这两个标头重新添加。map 块应放在 http 上下文中,而不是放在 server 内部。如果您不熟悉下面其余的 server 块,请参阅nginx server 块逐行说明,了解每条指令的作用。

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
server {
    listen 443 ssl;
    http2 on;
    server_name n8n.example.com;

    location / {
        proxy_pass http://127.0.0.1:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
        proxy_buffering off;
    }
}

proxy_read_timeout 是最容易遗漏的指令。其默认值为 60 秒,也适用于已升级的 WebSocket。因此,编辑器标签页在实例空闲时保持打开状态,最后一条消息通过后约 1 分钟就会断开连接。增大该值即可解决您返回长期打开的标签页时看到的提示。

sudo nginx -t && sudo systemctl reload nginx
sudo nginx -T | grep -iE 'proxy_http_version|upgrade|proxy_read_timeout'

nginx -T 会输出完整的运行中配置,而不是单个文件的内容,因此可以确认您的修改确实已加载。如果配置所在的文件没有被任何 include 行引用,正确的修复也会看起来没有效果。

然后告知 n8n 它位于代理之后,因为 n8n 会根据这些值生成 URL。

environment:
  - N8N_HOST=n8n.example.com
  - N8N_PROTOCOL=https
  - N8N_PORT=5678
  - N8N_PROXY_HOPS=1
  - N8N_WEBHOOK_URL=https://n8n.example.com/

N8N_PROXY_HOPS 默认为 0,这表示 n8n 会将连接地址视为客户端地址,并忽略 X-Forwarded-For。请将其设置为容器前面的代理数量。截至 August 2026,N8N_WEBHOOK_URL 是当前名称;旧名称 WEBHOOK_URL 仍可使用,但启动时会输出弃用警告。

Traefik 转发 WebSocket,然后发生超时

Traefik 无需中间件或额外标签即可转发 WebSocket 升级请求。因此,看到此提示的 Traefik 用户通常遇到的是超时,而不是缺少请求头。相关参数位于 entryPoint 上。截至 2026 年 8 月,在 Traefik v3 中,idleTimeout默认为 180 秒,readTimeout默认为 60 秒。

entryPoints:
  websecure:
    address: ":443"
    transport:
      respondingTimeouts:
        readTimeout: 0
        idleTimeout: 3600s

Caddy 会在 reverse_proxy中自动处理升级,无需为此配置指令。如果您完全无法修改代理,因为代理由其他人管理,请使用 N8N_PUSH_BACKEND=sse切换推送通道。SSE(服务器推送事件)是保持连接打开的普通 HTTP 响应,因此即使代理拒绝升级,也仍可正常工作;但过于激进的空闲超时仍会中断连接。选择代理本身是另一项决策,nginx、Caddy 和 Traefik 对比介绍了各自的运维成本。

容器确实在重启时

如果 RestartCount 持续增加,说明容器启动失败,Docker 正在重新启动它。将日志时间戳与每次重启时间对照,查看重启前紧接着出现的内容。几乎所有情况都属于以下4类:配置错误导致启动失败、n8n 无法连接数据库、运行后崩溃,以及因内存不足被终止。

先检查卷,因为权限问题最容易被忽略。官方镜像以非特权用户 node 运行,并将数据保存在 /home/node/.n8n。由 root 创建的绑定挂载无法让该用户写入,因此进程每次都会在启动时退出,重启策略则会将问题隐藏在重启循环中。

docker compose config
docker run --rm -it --entrypoint sh docker.n8n.io/n8nio/n8n -c 'id'
docker exec n8n ls -ld /home/node/.n8n

命名卷可以完全避免此问题,因为 Docker 会使用正确的所有权创建它。如果必须使用绑定挂载,请将 chown 主机目录的所有者设置为第一条命令输出的数字用户 ID。主机与容器之间的所有权映射值得了解一次;PUID 和 PGID 说明介绍了这些镜像如何确定负责写入文件的用户。

看起来像崩溃的内存不足终止

n8n 进程上方有两个独立的内存上限,它们的失败方式不同。容器控制组限制由内核强制执行:一旦超过该限制,进程会立即被终止,没有机会写入任何内容,OOMKilled 的值为 true。V8 堆上限由 Node.js 内部强制执行:一旦超过该上限,Node 会抛出堆错误并附带堆栈跟踪,然后自行退出,因此 OOMKilled 的值为 false。从浏览器看,这两种情况完全相同。从 docker inspect 看,它们只相差一个字段。

将 Node 堆上限设在容器限制以下。如果堆上限更高,V8 会一直分配内存,越过内核介入的临界点。因此,其垃圾回收器永远不会达到自身上限,最终总是发生更严重的故障,而且没有日志可供查看。

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    environment:
      - NODE_OPTIONS=--max-old-space-size=<MiB, below the container limit>
    deploy:
      resources:
        limits:
          memory: <your container limit>

根据 VPS 的实际内存配置选择这两个数值,并为数据库、代理和操作系统预留空间。docker stats --no-stream 会在输出当前使用量的同时显示当前生效的限制,这样可以确认你设置的限制就是 Docker 应用的限制。Compose 如何应用内存限制详细说明了同时设置多个限制时哪个键会生效。

执行数据会在后台持续增长

一次执行会在运行期间保存每个节点的输出,n8n 随后会存储这些数据。由此产生两个后果。一次运行的峰值内存取决于其中传递的最大数据批次,因此,一次处理一万行数据的工作流,与一次处理两百行数据的相同工作流,实际上是不同的程序。存储的数据副本会持续增长,直到被删除。

清理机制用于解决第二个问题。截至 2026 年 8 月,默认设置为启用清理,EXECUTIONS_DATA_MAX_AGE 为 336 小时(14 天),EXECUTIONS_DATA_PRUNE_MAX_COUNT 为 10000。对于运行 SQLite 的小型 VPS,这些值相对宽松,因为所有数据都存放在同一个文件中,并且为编辑器提供服务的同一进程还必须读写该文件。

environment:
  - EXECUTIONS_DATA_PRUNE=true
  - EXECUTIONS_DATA_MAX_AGE=72
  - EXECUTIONS_DATA_PRUNE_MAX_COUNT=1000
  - EXECUTIONS_DATA_SAVE_ON_SUCCESS=none
  - EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS=false

EXECUTIONS_DATA_SAVE_ON_SUCCESS=none 是激进设置。它会保留失败的执行以便调试,并丢弃成功的执行。请有意选择此设置,因为某个工作流可能产生错误输出但未报告错误;启用此设置后,您将没有数据可供检查。清理机制还会先将行标记为已删除,再在后续处理中移除它们。SQLite 会重新使用已释放的页面,而不是将其归还给文件系统,因此更改设置后,磁盘上的文件不会立即缩小。

如需减少峰值,而不是减少存储总量,请减少每次运行传递的数据量。将大型任务拆分为多个子工作流,让子工作流向父工作流返回小结果;使用 Loop Over Items 节点进行批处理;不要将完整数据集传入 Code 节点。

二进制文件不应一直驻留在内存中

N8N_DEFAULT_BINARY_DATA_MODE 默认为 default,会将二进制数据保存在正在运行的执行实例的内存中。节点下载的每个文件,以及传递给下一个节点的每个副本,都会一直保留到运行结束。一个工作流只要获取几个较大的附件,就可能使进程超过普通 JSON 处理不会触及的限制。因此,崩溃会集中出现在某个特定工作流中,而不是在运行一段时间后随机发生。

environment:
  - N8N_DEFAULT_BINARY_DATA_MODE=filesystem

使用 filesystem 后,二进制数据会写入 N8N_BINARY_DATA_STORAGE_PATH。该路径默认位于 n8n 用户目录中,因此会使用与其他数据相同的卷。切换前,请确认该卷有足够空间。N8N_PAYLOAD_SIZE_MAX 设置传入 webhook 负载的最大大小,单位为 MiB( mebibytes),默认值为 16。增大该值后可以接收更大的请求,但这会增加内存占用,需要由您承担相应的内存成本。

同一主机上的其他服务也会竞争相同的 RAM。如果 OOM kill 是在添加数据库容器后开始的,那么在 Docker 中或主机上运行数据库就是您当前需要作出的权衡。

重启策略,以及主机重启后的自动恢复

未设置重启策略的容器退出后会保持停止状态,主机重启后也不会自动恢复。restart: unless-stopped 会在这两种情况下重新启动容器,同时不会重新启动您手动停止的容器。restart: always 还会重新启动您主动停止的容器,但需要等 Docker 下次启动后才会执行。

n8n 提供一个健康检查端点,由 N8N_ENDPOINT_HEALTH 指定,默认值为 healthz。先从主机检查该端点,确认当前实例中的路径正确。

curl -fsS http://127.0.0.1:5678/healthz
docker exec n8n which wget curl
sudo systemctl is-enabled docker

健康检查本身不会重启任何容器。Compose 会将容器标记为不健康,然后停止处理。因此,健康检查必须配合重启策略或外部监控程序,才能产生实际效果。编写能够实际触发操作的健康检查 和 让堆栈在主机重启后再次启动 分别介绍这两部分。

始终不触发,但 n8n 正常运行

此工作流不会显示横幅,也不会重启。容器正在运行,编辑器可以正常使用,但预期的运行记录没有出现在执行列表中。大多数情况下,原因有以下4种。

  • 工作流未激活。Schedule Trigger 只会在生产路径上运行,因此在画布中测试不会创建任何计划任务。
  • 时区不是您所在的时区。GENERIC_TIMEZONE 默认使用 America/New_York,因此,在将 GENERIC_TIMEZONE 和 TZ 设置为您自己的时区前,设定为 09:00 的计划会在该时区的 09:00 触发。
  • 停机期间错过的触发不会在恢复后补执行。n8n 启动时才会注册触发器,因此容器重启期间到期的计划不会延迟执行。下一次运行时间是启动后的下一个到期时间。
  • 工作流被自动停用。N8N_WORKFLOW_AUTODEACTIVATION_ENABLED 默认关闭;启用后,持续崩溃的工作流会被取消发布。之后,它看起来就像从未被激活过一样。

打开执行列表并筛选此工作流。有失败记录表示工作流本身存在问题。如果它因访问同一台服务器上托管的其他服务而收到 429,该限制属于该服务,而不是 n8n;SearXNG 429 排查指南介绍了如何区分其自身的速率限制器与阻止您服务器 IP 的引擎。完全没有记录表示触发器存在问题,应从上述4种原因开始排查。

首先修改什么

  1. 在编辑任何文件前,先在您自己的容器中读取 STATUS、RestartCount 和 OOMKilled。
  2. 如果容器从未停止,请修复代理升级请求头和空闲超时设置。
  3. 如果 OOMKilled 为 true,请有意设置容器限制,将 Node 堆上限设在该限制以下,并将二进制数据切换到 filesystem。
  4. 如果没有任何任务触发,请确认工作流处于启用状态,并确认实例时区设置为您的时区。

这些设置大多只需在正常运行的安装上配置一次,之后通常无需再改动。如果您还在搭建该安装环境,请参阅使用 Docker 和 HTTPS 部署 n8n 的教程;这些设置都基于该教程。

FAQ

为什么容器正在运行时,n8n 编辑器会显示连接丢失提示?

编辑器会保持一个 WebSocket 连接,用于持续传输执行进度。如果反向代理没有转发 Connection: Upgrade 和 Upgrade: websocket 标头,或没有使用 HTTP/1.1 连接到上游,升级就不会完成。此时浏览器会不断重新连接,而 n8n 仍处于正常状态。在 nginx 中,需要配置 proxy_http_version 1.1 和两行 proxy_set_header,还要将 proxy_read_timeout 设置为长于默认 60 秒,以免空闲标签页被断开。使用 sudo nginx -T 检查当前生效的配置,不要只检查编辑过的文件。

如何区分因内存不足而被终止和普通崩溃?

运行 docker inspect n8n | grep -iE 'OOMKilled|ExitCode|RestartCount' 并查看 OOMKilled 标志。值为 True 表示进程超出内存限制,被内核终止。由于进程没有机会写入日志,容器日志中不会有有用信息。值为 False,且 docker logs 末尾有堆错误和堆栈跟踪,表示 Node.js 达到了自身的 V8 堆上限,然后自行退出。将 NODE_OPTIONS=--max-old-space-size 设置为低于容器内存限制的值,这样会触发第二种故障,因为这种故障会留下证据。

清理执行数据后,磁盘空间会立即释放吗?

不会。EXECUTIONS_DATA_PRUNE 会将旧执行记录标记为待删除,之后的清理过程才会删除这些记录。清理时间由 EXECUTIONS_DATA_PRUNE_HARD_DELETE_INTERVAL 设置。使用 SQLite 时,数据库文件还会重用已释放的页面,而不是将空间归还给文件系统。因此,即使记录已经删除,磁盘上的文件大小也会在一段时间内保持不变。将 EXECUTIONS_DATA_MAX_AGE 和 EXECUTIONS_DATA_PRUNE_MAX_COUNT 设置为适合当前主机的值,然后在第二天再次检查,不要立即检查。

为什么 n8n 重启期间,计划工作流没有运行?

n8n 会在进程启动时注册触发器,不会补执行停机期间到期的计划。因此,反复重启只会导致没有执行,而不会产生一批补执行任务。启动后的下一次执行时间,就是启动后下一个到期时间。如果必须确保任务不漏执行,请让外部调用方通过 webhook 触发工作流,将重试逻辑放在 n8n 外部。

健康检查会在 n8n 停止响应时重启它吗?

不会自动重启。Compose 健康检查只会将容器标记为 healthy 或 unhealthy。重启由 restart policy 负责,因此 restart: unless-stopped 会在容器退出后将其恢复,也会在主机重启后恢复它,前提是 Docker 服务已启用。使用 sudo systemctl is-enabled docker 确认这一点。如果要专门处理 unhealthy 状态,需要在 Docker 外部运行监控程序,读取容器状态并重启服务。