n8n VPS 持續離線?4 種故障診斷方法
n8n「連線中斷」不一定代表服務離線。本文教你分辨 WebSocket 橫幅、容器重新啟動迴圈、out of memory 終止,以及排程未觸發。
n8n 為何持續離線:4 種故障、1 種現象
「n8n 持續離線」這句話可能涵蓋 4 種不同故障,而每種故障都需要不同的修正方式。編輯器顯示連線中斷的橫幅,但容器仍正常執行。容器自行重新啟動。核心因 Node.js 使用過多記憶體而終止該程序。或者程序本身完全沒有問題,只是啟用中的工作流程始終沒有觸發。若修改了錯誤的設定,可能會花上一整個週末處理一個原本不存在的問題。
因此,在修改任何設定前,先確認發生的是哪一種故障。n8n 以單一 Node.js 程序執行,通常位於單一 Docker 容器內,前方則是負責 TLS termination(傳輸層安全性)的反向代理。每一層都可能以不同方式發生故障,而瀏覽器會以相同訊息回報這些故障。
依此順序診斷
在 VPS(virtual private server)上執行這些命令,並讀取該機器輸出的值。不要拿論壇文章中的數字比較。這裡重要的值描述的是你的伺服器,而不是其他人的伺服器。
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-streamdocker ps -a 的 STATUS 欄位表示容器目前狀態已持續多久。將這個時間與問題開始的時間比較。如果容器早在橫幅出現前就已持續執行,表示 n8n 從未離線。發生問題的是瀏覽器與後端之間的連線;下一節會介紹 websocket 路徑。
RestartCount 表示 Docker 重新啟動此容器的次數。記下這個數字,等待 1 分鐘後再讀取一次。如果你觀察期間數字持續增加,表示容器陷入重新啟動迴圈。每次重新啟動前的日誌行會指出原因。
OOMKilled 是 true 或 false 旗標。True 表示 Linux kernel 終止了程序,因為程序超過記憶體限制。限制可能來自容器本身,也可能來自整台機器。這個欄位可區分記憶體終止與其他類型的結束,因此應先讀取它,再進行推測。
ExitCode 是容器上次結束時傳回的值。你不需要記住每個代碼的意義。先讀取你的值,再讀取相同時間戳記的 docker logs 尾端內容。日誌尾端與 out of memory 旗標合併判讀,才能說明發生了什麼;單獨依賴其中一項可能會誤導你。
docker stats 會並列顯示即時記憶體使用量與目前套用的限制。讓它在第二個終端機中持續執行,觸發導致問題的工作流程,並觀察故障發生時數值如何變化。
「連線中斷」橫幅通常表示反向代理發生問題
n8n 編輯器會與後端維持一條長時間開啟的推送連線,將執行進度串流到畫布。預設使用 WebSocket,這是由 N8N_PUSH_BACKEND 選擇,預設值為 websocket。WebSocket 起初是一般的 HTTP 請求,並帶有 Connection: Upgrade 與 Upgrade: websocket 標頭。伺服器會回應 101 Switching Protocols,之後雙方就會使用同一個 TCP socket 進行雙向通訊。
有兩種情況會導致連線中斷,而且問題都出在代理層,而不是 n8n。代理以 HTTP/1.0 連線到上游,或移除了 upgrade 標頭,導致升級程序未完成,編輯器便會持續重新連線。另一種情況是升級成功,但代理之後因 socket 長時間沒有活動而將其關閉,因為沒有訊息的 WebSocket 看起來就像閒置連線。這兩種情況下,容器都正常運作。橫幅表示瀏覽器失去了這條通道。
在修改任何設定前,先於瀏覽器確認問題。開啟開發人員工具,前往 Network 分頁,篩選 WS,然後重新載入編輯器。推送請求應到達 101 Switching Protocols,並維持開啟狀態。若推送請求回傳一般狀態碼,或每隔幾秒重新出現一次,問題通常出在代理層。
讓編輯器維持連線的 nginx 設定
nginx 不會主動轉送升級請求。proxy_pass 預設會使用 HTTP/1.0 與後端通訊,而 Connection 和 Upgrade 是 hop-by-hop 標頭,nginx 轉送時會將其移除。您必須將這兩個標頭加回去。map 區塊應放在 http context 中,而不是放在 server 內。如果您不熟悉下方 server block 的其他內容,nginx server block 的逐行說明會說明每個指示詞的作用。
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。因此,若編輯器分頁在沒有活動的 instance 中保持開啟,最後一則訊息通過後約 1 分鐘就會失去連線。調高這個值,就能修正您回到長時間開啟的分頁時看到的提示。
sudo nginx -t && sudo systemctl reload nginx
sudo nginx -T | grep -iE 'proxy_http_version|upgrade|proxy_read_timeout'nginx -T 會輸出完整的執行中設定,而不是單一檔案,因此可確認您的修改確實已載入。若設定檔所在的檔案沒有任何 include 行讀取它,正確的修正就會看起來毫無作用。
接著告訴 n8n 它位於 proxy 後方,因為 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 會將連入位址視為 client 位址,並忽略 X-Forwarded-For。請將它設為容器前方 proxy 的數量。截至 August 2026,N8N_WEBHOOK_URL 是目前使用的名稱;較舊的 WEBHOOK_URL 仍可運作,但啟動時會顯示淘汰警告。
Traefik 會轉送 WebSocket,但之後因逾時而中斷
Traefik 不需 middleware 或額外 labels,就會轉送 WebSocket upgrade。因此,Traefik 使用者看到這個提示時,通常遇到的是逾時,而不是缺少 header。相關設定位於 entryPoint。在 2026 年 8 月的 Traefik v3 中,idleTimeout 預設為 180 秒,readTimeout 預設為 60 秒。
entryPoints:
websecure:
address: ":443"
transport:
respondingTimeouts:
readTimeout: 0
idleTimeout: 3600sCaddy 會在 reverse_proxy 中自動處理 upgrade,不需要額外指令。如果完全無法變更 proxy,因為 proxy 由其他人管理,請使用 N8N_PUSH_BACKEND=sse 切換 push channel。SSE(server-sent events)是保持開啟的正常 HTTP 回應,因此即使 proxy 拒絕 upgrade,也能持續運作;但過於積極的 idle timeout 仍會中斷連線。選擇 proxy 本身是另一項決策,nginx、Caddy 與 Traefik 的比較說明各方案的維運成本。
容器確實正在重新啟動時
如果 RestartCount 持續上升,表示容器發生故障,而 Docker 正在將它重新啟動。將日誌時間戳與每次重新啟動時間對照,查看重新啟動前緊接著出現的內容。幾乎所有情況都可歸納為 4 個原因:設定錯誤導致啟動失敗、n8n 無法連線的資料庫、服務執行後崩潰,以及記憶體終止程序。
先檢查 volume,因為權限問題最不容易察覺。官方映像檔會以非特權使用者 node 執行,並將資料儲存在 /home/node/.n8n。由 root 建立的 bind mount 無法讓該使用者寫入,因此程序每次都會在啟動時結束,而 restart policy 會將這個問題隱藏在反覆重新啟動的循環中。
docker compose config
docker run --rm -it --entrypoint sh docker.n8n.io/n8nio/n8n -c 'id'
docker exec n8n ls -ld /home/node/.n8n使用 named volume 可完全避免這個問題,因為 Docker 會以正確的擁有權建立它。如果需要使用 bind mount,請將主機目錄的擁有權 chown 為第一個指令輸出的數值使用者 ID。主機與容器之間的擁有權對應值得了解一次;PUID 與 PGID 說明會解釋這些映像檔如何決定檔案的寫入者。
看似當機的記憶體不足終止
n8n 程序上方有兩個不同的記憶體上限,觸發時的行為也不同。容器的 control group 限制由核心強制執行:超過限制後,程序會立即被終止,沒有機會寫入任何內容,且 OOMKilled 會讀取為 true。V8 heap 限制則由 Node.js 內部執行:超過限制後,Node 會擲出 heap 錯誤並附上 stack trace,然後自行結束,因此 OOMKilled 會讀取為 false。從瀏覽器看來,兩者完全相同。從 docker inspect 來看,兩者只差一個欄位。
將 Node heap 上限設在容器限制以下。如果 heap 上限較高,V8 會持續配置記憶體,超過核心介入的臨界點,因此其 garbage collector 永遠不會達到自身上限,最後總是發生較嚴重的失敗,而且沒有 log 可供檢查。
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 實際提供的資源設定這兩個數值,並為資料庫、proxy 與作業系統保留空間。docker stats --no-stream 會在目前使用量旁顯示生效中的限制,因此你可以確認自己設定的限制就是 Docker 套用的限制。Compose 記憶體限制的套用方式 說明同時設定多個限制時,哪個 key 會生效。
執行資料會持續累積
單次執行會在工作流程進行期間保存每個節點的輸出,n8n 之後也會儲存這些資料。這會產生兩個結果。單次執行的記憶體峰值取決於一次傳遞的最大資料批次,因此,一次處理一萬列的工作流程,與一次處理兩百列的相同工作流程,實際上是不同的程式。儲存的副本也會持續增加,直到系統將其刪除。
Pruning 可處理第二個問題。截至 2026 年 8 月,預設值為啟用 pruning、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=falseEXECUTIONS_DATA_SAVE_ON_SUCCESS=none 是積極清理的設定。它會保留失敗的執行記錄以便偵錯,並捨棄成功的執行記錄。請刻意決定是否使用此設定,因為工作流程可能在未回報錯誤的情況下產生錯誤輸出;若捨棄成功記錄,之後便沒有資料可供檢查。Pruning 也會先將資料列標記為已刪除,再於後續程序中移除;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 policy 的容器在結束後會保持停止狀態,主機重新開機後也不會自行啟動。restart: unless-stopped 會在這兩種情況下重新啟動容器,但仍會遵守您手動停止的容器。restart: always 也會在 Docker 下次啟動時,重新啟動您刻意停止的容器。
n8n 提供由 N8N_ENDPOINT_HEALTH 指定名稱的 health endpoint,預設值為 healthz。先從主機檢查該端點,確認這條路徑在您的執行個體上正確。
curl -fsS http://127.0.0.1:5678/healthz
docker exec n8n which wget curl
sudo systemctl is-enabled dockerhealthcheck 本身不會重新啟動任何容器。Compose 只會將容器標記為 unhealthy,然後停止處理。因此,healthcheck 必須搭配 restart policy,或由外部 watcher 配合,才能產生實際作用。撰寫真正會發揮作用的 healthcheck 與 讓 stack 在重新開機後再次啟動 分別說明這兩個部分。
始終不觸發的工作流程,但 n8n 運作正常
這個問題不會顯示橫幅,也不會重新啟動。容器正在執行,編輯器可正常使用,但你預期的執行記錄沒有出現在執行清單中。大多數情況可歸因於以下 4 個原因。
- 工作流程未啟用。Schedule Trigger 只會在 production path 上執行,因此在畫布中測試不會建立排程。
- 時區不是你所在的時區。
GENERIC_TIMEZONE預設為America/New_York,因此排程設為 09:00 時,會先依該時區在 09:00 觸發,直到你將GENERIC_TIMEZONE和TZ設定為自己的時區。 - 停機期間的執行不會在事後補執行。n8n 啟動時才會註冊觸發器,因此容器重新啟動期間到期的排程不會延後執行。下一次執行時間是啟動後下一個到期時間。
- 工作流程遭系統停用。
N8N_WORKFLOW_AUTODEACTIVATION_ENABLED預設為關閉;啟用後,持續崩潰的工作流程會被取消發布,之後看起來就完全像是從未有人啟用過。
開啟執行清單,並篩選該工作流程。若有失敗的項目,問題出在工作流程本身。若它因對同一台主機上的其他服務發出 429 而失敗,限制來自該服務,而不是 n8n;SearXNG 429 排除指南說明如何分辨是該服務自己的速率限制器,還是各引擎封鎖了你的伺服器 IP。完全沒有項目表示是觸發器問題,應從上述 4 個原因開始檢查。
優先變更項目
- 編輯任何檔案前,先在自己的容器中讀取
STATUS、RestartCount和OOMKilled。 - 如果容器從未停止,請修正 proxy upgrade headers 和 idle timeout。
- 如果
OOMKilled為 true,請刻意設定容器限制,將 Node heap 上限設在該限制以下,並將二進位資料切換至filesystem。 - 如果完全沒有觸發任何項目,請確認 workflow 目前已啟用,且 instance timezone 設定為你所在的時區。
這些設定大多只需在可正常運作的安裝上設定一次,之後即可維持不變。如果你仍在建置該安裝環境,請參閱使用 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,表示 kernel 因程序超過記憶體限制而將其終止。容器日誌中不會有可用資訊,因為程序沒有機會寫入日誌。若為 False,且 docker logs 結尾出現 heap 錯誤與 stack trace,表示 Node.js 達到自身的 V8 heap 上限後自行結束。請將 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 重新啟動時,我排程的 workflow 沒有執行?
n8n 會在程序啟動時註冊觸發條件,不會補執行服務停止期間到期的排程。因此,反覆重新啟動只會造成沒有執行結果,不會在恢復後一次執行多次。下一次執行時間會是啟動後下一個到期時間。如果執行不可遺漏,請由外部呼叫端存取 webhook 來驅動 workflow,讓重試邏輯位於 n8n 外部。
healthcheck 會在 n8n 無回應時重新啟動 n8n 嗎?
不會自行重新啟動。Compose healthcheck 只會將容器標記為 healthy 或 unhealthy。重新啟動由 restart policy 負責,因此 restart: unless-stopped 會在容器結束後將其恢復,也會在主機重新開機後恢復,但前提是 Docker service 已啟用。請使用 sudo systemctl is-enabled docker 確認這項設定。若要針對 unhealthy 狀態採取動作,必須在 Docker 外部使用 watcher 讀取狀態並重新啟動 service。