n8nがVPSでオフラインになる原因と切り分け方
n8nのオフライン表示は、websocketバナー、再起動ループ、out of memoryによる強制終了、スケジュール未実行の4種類があります。Dockerの状態とログで原因を切り分けます。
n8n がオフラインになり続ける理由: 4 つの障害、1 つの症状
「n8n がオフラインになり続ける」という表現は、異なる 4 つの障害をまとめたものです。それぞれ必要な対処が異なります。コンテナが正常に動作しているのに、エディターには接続が失われたことを示すバナーが表示される場合があります。コンテナが自動的に再起動する場合もあります。カーネルがメモリを使いすぎた Node.js プロセスを強制終了することもあります。あるいは、プロセスには問題がなく、実行可能なワークフローが一度も起動されていないだけかもしれません。誤った設定を変更すると、実際には存在しない問題に週末を費やすことになります。
そのため、設定を変更する前に、どの障害が発生しているのかを特定してください。n8n は通常、TLS (transport layer security) を終端するリバースプロキシの背後で、1 つの Docker コンテナ内の単一の Node.js プロセスとして動作します。これらの各層には、それぞれ固有の障害要因がありますが、ブラウザーはすべて同じメッセージで報告します。
この順序で診断します
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-streamdocker ps -a の STATUS 列は、コンテナが現在の状態になってからの経過時間を示します。この値を問題が始まった時刻と比較します。バナーが表示されるよりずっと前からコンテナが起動していた場合、n8n は停止していません。問題はブラウザーとバックエンドの接続にあり、次のセクションで扱う websocket の経路に関係しています。
RestartCount は、Docker がこのコンテナを再起動した回数です。数値を記録して 1 分待ち、もう一度確認します。監視中に数値が増える場合は、再起動ループが発生しています。各再起動の直前に出力されたログ行に原因が記録されています。
OOMKilled は true または false のフラグです。true の場合、コンテナ自体またはマシン全体のメモリ制限を超えたため、Linux カーネルがプロセスを強制終了しています。この 1 つのフィールドで、メモリ不足による終了と他の終了原因を区別できます。そのため、推測する前に確認します。
ExitCode は、コンテナが最後に終了したときの終了コードです。各コードの意味を暗記する必要はありません。自分の値を確認し、同じタイムスタンプにおける docker logs の末尾も確認します。ログの末尾と out of memory フラグを組み合わせると何が起きたかを判断できますが、どちらか一方だけでは誤った判断につながることがあります。
docker stats は、現在のメモリ使用量と適用中の上限を並べて表示します。別のターミナルで実行したままにし、問題が発生する workflow を実行します。失敗中に数値がどのように変化するかを確認します。
接続切断バナーの原因は通常、リバースプロキシです
n8n エディターは、実行の進行状況をキャンバスにストリーミングするため、バックエンドへの長時間接続する push 接続を 1 本維持します。デフォルトでは、この接続に WebSocket を使用します。N8N_PUSH_BACKEND で接続方式を選択し、デフォルト値は websocket です。WebSocket は、Connection: Upgrade と Upgrade: websocket のヘッダーを含む通常の HTTP リクエストとして開始します。サーバーは 101 Switching Protocols を返し、その後は双方が同じ TCP ソケットを双方向で使用します。
この接続を壊す原因は 2 つあります。どちらも n8n ではなく、プロキシ側にあります。プロキシが上流に HTTP/1.0 で接続するか、upgrade ヘッダーを削除すると、プロトコルの切り替えが発生せず、エディターは再接続を繰り返します。もう 1 つは、upgrade が成功した後、プロキシが一定時間通信のないソケットを閉じるケースです。メッセージのない WebSocket は、アイドル状態の接続と同じように見えるためです。どちらの場合も、コンテナは正常です。バナーは、ブラウザーが接続経路を失ったことを示しています。
他の設定を変更する前に、ブラウザーで確認してください。開発者ツールを開き、Network タブで WS に絞り込み、エディターを再読み込みします。push リクエストは 101 Switching Protocols に到達し、接続を維持するはずです。push リクエストが通常のステータスコードを返す場合、または数秒ごとに再表示される場合は、プロキシに問題があります。
エディターの接続を維持する nginx 設定
nginx は、明示的に指定しない限り upgrade を転送しません。proxy_pass はデフォルトでバックエンドと HTTP/1.0 で通信し、Connection と Upgrade は nginx が転送時に削除する hop-by-hop ヘッダーです。両方を再設定する必要があります。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 秒で、upgrade 後の WebSocket にも適用されます。そのため、通信がない状態でエディターのタブを開いたままにすると、最後のメッセージから約 1 分後に接続が失われます。この値を引き上げると、開いたままにしていたタブへ戻ったときに表示されるバナーを解消できます。
sudo nginx -t && sudo systemctl reload nginx
sudo nginx -T | grep -iE 'proxy_http_version|upgrade|proxy_read_timeout'nginx -T は 1 つのファイルではなく、実行中の設定全体を出力します。そのため、編集内容が実際に読み込まれていることを確認できます。正しい修正をしても効果がない場合、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 を無視することを意味します。コンテナの前段にあるプロキシの数を設定してください。2026 年 8 月時点では 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: 3600sCaddy は reverse_proxy でアップグレードを自動的に処理するため、そのためのディレクティブは必要ありません。プロキシを変更できない場合は、他者が管理している場合など、N8N_PUSH_BACKEND=sse に切り替えてプッシュ用チャネルを変更します。SSE(server-sent events)は接続を維持した通常の HTTP レスポンスです。そのため、アップグレードを拒否するプロキシでも利用できます。ただし、アイドルタイムアウトが短く設定されている場合は接続が切断されます。プロキシ自体の選択は別の判断事項です。nginx、Caddy、Traefik の比較では、それぞれの運用コストを説明しています。
コンテナが実際に再起動を繰り返している場合
RestartCountが増加している場合、コンテナで障害が発生し、Docker が再起動しています。ログのタイムスタンプを各再起動の時刻と照合し、その直前に出力された内容を確認します。ほとんどの場合、原因は次の4つです。起動を停止させる設定エラー、n8n が接続できないデータベース、起動後のクラッシュ、メモリによる強制終了です。
まずボリュームを確認します。権限の問題は気付きにくいためです。公式イメージは非特権ユーザー 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/.n8nnamed volume なら、Docker が適切な所有者で作成するため、この問題を完全に回避できます。bind mount が必要な場合は、chown でホスト側のディレクトリの所有者を、最初のコマンドで表示された数値の user id に変更します。ホストとコンテナ間の所有者マッピングは、一度理解しておく価値があります。PUID と PGID の解説では、これらのイメージがファイルを書き込むユーザーをどのように決定するかを説明しています。
クラッシュに見える out of memory kill
n8n プロセスには、別々のメモリ上限が 2 つあります。この 2 つは異なる動作で失敗します。コンテナの control group の制限は kernel によって適用されます。この制限を超えると、プロセスは直ちに kill されます。何も書き込む機会はなく、OOMKilled は true になります。V8 heap の制限は Node.js 内部で適用されます。この制限を超えると、Node は stack trace 付きの heap error をスローして自動的に終了するため、OOMKilled は false になります。ブラウザーから見ると、これらは同じように見えます。docker inspect から見ると、両者は 1 つのフィールドしか違いません。
Node の heap 上限は、コンテナの制限より低く設定してください。heap 上限のほうが高いと、V8 は kernel が介入する境界を越えて割り当てを続けます。そのため、garbage collector が独自の上限に到達せず、ログを確認できない、より深刻な失敗が常に発生します。
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>両方の値は、データベース、proxy、operating system のための余裕を残し、実際に VPS が使用できるメモリ量を基準に決めてください。docker stats --no-stream は現在の使用量と適用中の制限を並べて表示します。これにより、記述した制限が Docker に適用された制限と一致するか確認できます。Compose のメモリ制限が適用される仕組みでは、複数の制限を設定した場合にどの key が優先されるかを説明します。
実行データは気付かないうちに増加します
1 回の実行では、実行中にすべてのノードが出力したデータを保持し、n8n はそのデータを保存します。ここから 2 つの点が分かります。1 回の実行で使用するメモリのピークは、処理するデータの最大バッチサイズで決まります。そのため、1 回に 10000 行を処理するワークフローは、1 回に 200 行を処理する同じワークフローとは別のプログラムです。また、保存されたデータは、何かが削除するまで増え続けます。
2 つ目の問題には、pruning を使用します。2026 年 8 月時点のデフォルトでは pruning が有効で、EXECUTIONS_DATA_MAX_AGE は 336 時間 (14 日)、EXECUTIONS_DATA_PRUNE_MAX_COUNT は 10000 です。すべてのデータを 1 つのファイルに保持し、エディターを提供する同じプロセスがそのファイルを読み書きする、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 は解放されたページを OS に返さず再利用するため、設定を変更してもディスク上のファイルはすぐには小さくなりません。
保存される合計量ではなくピーク使用量を減らすには、1 回の実行で移動するデータ量を減らします。大規模な処理を、親ワークフローには小さな結果だけを返すサブワークフローに分割します。Loop Over Items ノードでバッチ処理し、データセット全体を Code ノードに渡さないようにします。
バイナリファイルをメモリ上で扱わない
N8N_DEFAULT_BINARY_DATA_MODEの既定値はdefaultです。この設定では、実行中の処理のメモリにバイナリデータを保持します。node がダウンロードするすべてのファイルと、次の node に渡すすべてのコピーが、実行終了までメモリに残ります。数個の大きな添付ファイルを取得するだけで、1つの workflow が通常の JSON 処理では到達しないメモリ上限を超えることがあります。そのため、クラッシュは時刻ではなく、特定の workflow に伴って発生します。
environment:
- N8N_DEFAULT_BINARY_DATA_MODE=filesystemfilesystemを使用すると、バイナリデータはN8N_BINARY_DATA_STORAGE_PATHの下に書き込まれます。N8N_BINARY_DATA_STORAGE_PATHは既定で n8n の user folder 内にあるため、他のデータと同じ volume に保存されます。切り替える前に、volume に十分な空き容量があることを確認してください。N8N_PAYLOAD_SIZE_MAXは、受信する webhook payload の最大サイズを MiB(mebibytes)単位で指定し、既定値は 16 です。この値を上げると、より大きな request を受け付けられますが、その分のメモリ消費を受け入れることになります。
同じサーバー上で動作する他のサービスも、同じ RAM を使用します。データベースの container を追加してから 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 はコンテナを unhealthy として記録して処理を停止するため、ヘルスチェックを有効にするには、再起動ポリシーまたは別途動作する外部監視が必要です。実際に機能するヘルスチェックの作成と再起動後にスタックを再起動する設定で、両方の方法を説明します。
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つの原因です。
最初に変更する項目
- ファイルを編集する前に、自分のコンテナ上で
STATUS、RestartCount、OOMKilledを確認します。 - コンテナが停止していない場合は、プロキシのアップグレード用ヘッダーとアイドルタイムアウトを修正します。
OOMKilledが true の場合は、意図した値でコンテナの上限を設定し、Node のヒープ上限をその値より低くして、バイナリデータをfilesystemに切り替えます。- 何も実行されなかった場合は、ワークフローが有効になっていることと、インスタンスのタイムゾーンが自分のタイムゾーンであることを確認します。
この大半は、動作するインストール環境の上に一度設定すれば、以後は変更する必要がない項目です。まだインストール環境を構築中であれば、HTTPS 対応の Docker 上の n8n 手順が、これらの設定の基盤になります。
FAQ
n8n エディターでコンテナが実行中なのに接続切断のバナーが表示されるのはなぜですか?
エディターは、実行の進行状況をストリーミングするために WebSocket 接続を維持します。リバースプロキシが Connection: Upgrade と Upgrade: websocket ヘッダーを転送しない場合、または上流接続で HTTP/1.1 を使用しない場合、アップグレードが完了しません。そのため、n8n が正常でもブラウザーは再接続を繰り返します。nginx では proxy_http_version 1.1 と proxy_set_header の両方の行が必要です。また、アイドル状態のタブが切断されないように、デフォルトの 60 秒より長い proxy_read_timeout を設定します。編集したファイルではなく、sudo nginx -T で実行中の設定を確認してください。
Out of memory による kill と通常のクラッシュを見分けるにはどうすればよいですか?
docker inspect n8n | grep -iE 'OOMKilled|ExitCode|RestartCount' を実行し、OOMKilled フラグを確認します。True の場合、メモリ制限を超えたため kernel がプロセスを kill しています。この場合、プロセスがログを書き込む機会を得ていないため、コンテナログには有用な情報が残りません。False で、docker logs の末尾に heap error と stack trace がある場合は、Node.js が V8 の heap 上限に達し、自身で終了しています。NODE_OPTIONS=--max-old-space-size をコンテナの制限より小さく設定してください。これにより、証拠が残る後者の障害にできます。
実行データを prune すると、すぐにディスク容量が解放されますか?
いいえ。EXECUTIONS_DATA_PRUNE は古い実行データを削除対象としてマークし、後続の処理で削除します。この処理のスケジュールは EXECUTIONS_DATA_PRUNE_HARD_DELETE_INTERVAL で設定します。SQLite では、ファイルは解放されたページをファイルシステムに返さず再利用します。そのため、行を削除した後もしばらくディスク上のサイズは変わりません。EXECUTIONS_DATA_MAX_AGE と EXECUTIONS_DATA_PRUNE_MAX_COUNT を環境に適した値に設定し、すぐにではなく翌日に再度確認してください。
n8n の再起動中に、スケジュール済みの workflow が実行されなかったのはなぜですか?
n8n はプロセスの起動時に trigger を登録します。停止中に実行時刻を迎えたスケジュールを、後から再実行することはありません。そのため、再起動を繰り返してもキャッチアップ実行が一斉に発生するのではなく、起動後に次に到来する時刻まで実行されません。欠落させられない実行が必要な場合は、webhook を呼び出す外部 caller から workflow を起動してください。これにより、retry ロジックを n8n の外部に置けます。
healthcheck によって、応答しなくなった n8n は再起動されますか?
それだけでは再起動されません。Compose の healthcheck は、コンテナを healthy または unhealthy としてマークするだけです。再起動は restart policy の役割です。そのため、restart: unless-stopped が、コンテナの終了後にコンテナを復旧させます。Docker service が有効であれば、host の再起動後にも復旧させます。sudo systemctl is-enabled docker で確認してください。unhealthy の状態を条件に処理するには、Docker 外部の watcher が status を読み取り、service を再起動する必要があります。