n8nをVPSにDockerで構築する方法。HTTPS対応とPostgres設定
Docker ComposeとPostgresを用いてn8nをVPS上に構築する手順を解説します。初心者が躓きやすいWEBHOOK_URLの設定やencryption-keyのトラブル、OOM killerによるコンテナ停止を防ぐメモリ要件など、実用的な構築ノウハウをまとめました。
作成するもの
n8nはワークフロー自動化ツールです。webhook、schedule、form submissionなどのtriggerによって、APIの呼び出し、データの整形、他システムへの書き込みを行う一連のnodeが実行されます。n8nはあらゆるモデルプロバイダーやデータベースと連携できるため、AIエージェントのワークフローにおける標準的なツールとなっています。docker runを使えば、2分でエディタを起動できます。本ガイドでは、残りの90%の工程について解説します。デフォルトのSQLiteファイルの代わりにPostgresを使用して永続化し、HTTPS経由でアクセス可能にし、そして多くの人が躓くポイントである「外部からアクセス可能なURLをwebhookに割り当てる設定」を行います。
完成したスタックは、1つのDocker network上で動作する2つのcontainerで構成されます。n8n本体と、ワークフローおよびcredentialsを保存するPostgresデータベースです。ホスト上のreverse proxyがTLSを終端し、localhostのn8nへ転送するため、インターネットに直接公開されるのはこのproxyのみとなります。この構成は、2026 self-hosting shortlistに掲載されている他のサービスと同様の構成です。
前提条件と制限事項
少なくとも 1 GB の RAM を搭載した VPS が必要です。ワークフローの負荷が増えるとメモリを消費するため、2 GB 以上のプランを推奨します。実行プロセスと Node.js ランタイムがメモリを消費し、実行中に OOM killer によってコンテナが強制終了されるトラブルを避けるためです。開始時は 1 vCPU で問題ありません。
ドメインまたはサブドメイン(例: n8n.example.com)が必要です。証明書を取得する前に、A record が VPS のパブリック IP を指していることを確認してください。プロキシに対して Port 80 と 443 を開放する必要があります。n8n の Port 5678 は、インターネットに公開してはいけません。Docker Engine と Compose plugin が必要です。もし docker compose version で docker: 'compose' is not a docker command のエラーが出る場合は、古いスタンドアロンのバイナリを使用しています。その場合、plugin は sudo apt install docker-compose-plugin です。
テストには SQLite、本番環境には Postgres を推奨します
n8n のデフォルトデータベースは /home/node/.n8n/database.sqlite にある SQLite ファイルです。動作確認用であれば問題ありません。ボリュームをマウントしなければ、コンテナの再作成時にデータは消失します。Postgres への移行が必要な理由は、処理速度ではありません。SQLite は単一の書き込みロックを使用するため、複数のワークフローを同時に実行する場合や、将来的に必要となる queue mode を使用する場合、並行処理において SQLITE_BUSY: database is locked が発生します。Postgres にはそのような制限はなく、pg_dump によるバックアップも可能です。また、n8n の公式ドキュメントでは、運用サーバーには Postgres を使用することを前提としています。後から切り替えるには手動でのデータ移行が必要になるため、重要な環境では最初から Postgres を使用してください。
DNSとfirewall
レコードの設定とポートの開放を最初に行ってください。そうしないと、名前解決に失敗して後の証明書取得ステップでエラーが発生します。
dig +short n8n.example.com
curl -s ifconfig.me
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow OpenSSH
sudo ufw enable5678ポートは開放しないでください。compose fileでn8nを127.0.0.1:5678にバインドしているため、ホストのreverse proxyからのみアクセス可能です。ufw allow 5678を行うと、この隔離状態が解除されてしまいます。
Compose file
作業ディレクトリを作成し、docker-compose.ymlを作成します。これですべてのスタックが構成されます。2つのservice、1つのprivate network、2つのnamed volumeが含まれます。
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: n8n
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- n8n_net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
interval: 10s
timeout: 5s
retries: 5
n8n:
image: docker.n8n.io/n8nio/n8n:2.29.10
restart: unless-stopped
ports:
- "127.0.0.1:5678:5678"
environment:
- N8N_HOST=n8n.example.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.example.com/
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- N8N_PROXY_HOPS=1
- GENERIC_TIMEZONE=Europe/London
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- n8n_data:/home/node/.n8n
networks:
- n8n_net
depends_on:
postgres:
condition: service_healthy
volumes:
postgres_data:
n8n_data:
networks:
n8n_net:重要な決定事項をいくつか述べます。DB_POSTGRESDB_HOST=postgresはservice nameであり、Dockerは共有network上でこれを解決します。localhostではありません。n8n container内では、localhostはn8n自身を指します。condition: service_healthyを伴うdepends_onにより、起動時にn8nがPostgresより先に起動するのを防ぎます。これがない場合、n8nは起動後にデータベースが見つからず、終了します。/home/node/.n8nにあるnamed volume n8n_dataには、encryption keyと、SQLiteの場合はdatabaseが保存されます。このdirectoryは決して紛失しないでください。imageは特定のversionに固定してください。latestは使用しないでください。理由は後述のupgrade sectionに記載されています。
secrets file
compose file にパスワードを記述しないでください。その隣に .env file を作成し、Compose が自動的に読み込むようにします。パスワードは、真の乱数として生成してください。
printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
printf 'N8N_ENCRYPTION_KEY=%s\n' "$(openssl rand -hex 32)" >> .env
chmod 600 .envN8N_ENCRYPTION_KEY は、ここで最も重要な文字列です。これは、保存されるすべての認証情報が暗号化される際に使用されるキーです。n8n に自動生成させるのではなく、明示的に設定してください。自分で生成した値であれば、記録して復元することが可能です。n8n がこのキーを使用して最初の認証情報を暗号化すると、そのキーを変更するとすべての認証情報が復号できなくなります。そのため、今一度だけ設定し、その行には二度と手を加えないでください。
Webhookの動作を決定する環境変数
n8nが外部に対して自身の情報をどのように伝えるかは、4つの変数によって制御されます。これらの設定ミスは、n8nのサポートにおける最も多い質問です。
N8N_HOSTは公開ホスト名、n8n.example.comです。プロキシを使用している場合にこれをデフォルトのlocalhostのままにすると、エディタはブラウザ内でlocalhostから自身のAPIを読み込もうとし、失敗します。N8N_PROTOCOL=httpsは、n8nがTLS経由で提供されていることを示します。これにより、セッションクッキーSecureに属性が付与され、https://URLが生成されます。N8N_PORT=5678は、コンテナ内部でn8nが待機するポートです。これは公開ポートではありません。443ポートはプロキシが使用します。WEBHOOK_URL=https://n8n.example.com/は、最も注意が必要な変数です。n8nはこれらの値からWebhookのアドレスを構築し、StripeやGitHub、その他の外部サービスに貼り付けるためのアドレスを表示します。この変数が未設定または誤っている場合、n8nはN8N_HOST:N8N_PORTを使用します。その結果、https://n8n.example.com:5678/webhook/...や、さらに深刻な場合にはhttp://localhost:5678/webhook/...が出力されます。これらはエラーを出さず、一見正しく見えますが、インターネットからは到達不可能です。そのため、呼び出し元のリクエストは届かず、エラーも発生しません。末尾にスラッシュを含む正確な公開ベースURLを設定してください。その後、Webhookノードにポート番号が含まれないURLが表示されていることを確認してください。
N8N_PROXY_HOPS=1 は、n8nのExpressサーバーに対して、前段にあるプロキシを信頼するように指示します。これにより、レート制限やクライアントIPを読み取る機能が、プロキシのIPではなく実際のIPを認識できるようになります。ここで意図的に設定しない変数が N8N_RUNNERS_ENABLED です。Code-nodeのロジックを別個のサンドボックスプロセスで実行するtask runnerは、バージョン1.69からデフォルトとなり、本ガイドで指定する2.x系では必須となっています。そのため、以前のオプトイン方式は非推奨となりました。今すぐ設定しても、n8nは設定を削除するよう通知するだけで動作します。
初回起動
docker compose up -d
docker compose ps
docker compose logs -f n8n正常な初回起動では、最後に Editor is now accessible via: が表示され、その上に n8n ready on ..., port 5678 が表示されます。docker compose ps には両方のコンテナの Up が表示され、postgres は (healthy) と表示される必要があります。n8n が Restarting ループに陥っている場合は、ログを確認してください。原因のほとんどは、後述するデータベース接続またはボリュームの権限の問題です。
リバースプロキシを使用したTLS
n8n自体は 5678 ポートで HTTP を使用します。HTTPS の終端処理は前段のコンポーネントが行います。主な方法は2つあります。
すでに複数のコンテナを実行している場合は、いくつかの label を設定して TLS証明書を自動発行する Traefik リバースプロキシ の背後に n8n を配置してください。Traefik が証明書の要求と更新を自動で行います。
このサーバーで唯一のアプリとして実行する場合は、Let's Encrypt 証明書を使用した nginx virtual host がより簡単です。Ubuntu 24.04 用の Certbot と nginx TLS 設定 を使用して証明書を取得し、以下の server block を使用してください。
server {
listen 443 ssl;
server_name n8n.example.com;
ssl_certificate /etc/letsencrypt/live/n8n.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/n8n.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:5678;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600;
client_max_body_size 16m;
}
}Upgrade と Connection "upgrade" ヘッダーは必須です。n8n は WebSocket を介してエディタに実行状況の更新を送信します。これら2行がない場合、ログインページは読み込まれますが、接続切れのバナーが表示されて停止します。proxy_read_timeout 3600 は、実行中の処理が nginx のデフォルト設定である 60 秒で切断されるのを防ぎます。X-Forwarded-Proto $scheme ヘッダーは N8N_PROXY_HOPS=1 と対になるものです。プロキシが HTTP で通信していても、元のリクエストが HTTPS であったことを n8n に伝えます。これにより、n8n が接続を安全でないと判断して、自身の cookie を拒否するのを防ぎます。
実践的な最初のワークフロー
https://n8n.example.com/を開き、オーナーアカウントを作成して(次のセクション参照)、パスが動作することを確認するための最小構成のワークフローを作成します。構成は、Webhookの受信、HTTPコール、レスポンスの返信です。
- Webhookノードを追加します。Methodを
POSTに設定し、Pathをhelloなどの値に設定してください。Test URLとProduction URLの2つのURLが表示されます。「Webhookが動作しない」という問題の多くは、この違いに起因します。Test URLは、Listen for test eventをクリックしている間、一度のコールに対してのみ応答し、その後期限切れとなります。Production URLは、ワークフローがActiveである限り常に応答します。 - その後にHTTP Requestノードを追加し、公開されているJSON APIを指定します。
https://api.github.com/zenへのGETリクエストは1行の文字列を返します。これで十分です。 - Respond to Webhookノードを追加します。WebhookノードのRespondオプションを"Using Respond to Webhook node"に設定してください。これにより、呼び出し元にHTTPノードの出力が返されます。
- ワークフローをActive(右上)に切り替え、
curl -X POST https://n8n.example.com/webhook/helloを呼び出します。POST受信、APIコール、レスポンス返信という、一般的な自動化の基本形が実行され、結果が返ってくるはずです。
スケジュール実行を行う場合は、WebhookノードをSchedule Triggerに置き換え、モデルのendpointを呼び出すようにします。同じVPS上で動作するOllamaを使用すると、夜間バッチの要約処理などを簡単に構築できます。
ユーザー管理(Basic Authではありません)
古い n8n のガイドでは N8N_BASIC_AUTH_ACTIVE=true を設定するよう記載されています。しかし、これらの変数は n8n 1.0 で削除されたため、現在は機能しません。現在の認証方式は owner account です。エディタを初めて読み込む際、n8n はメールアドレスとパスワードによる owner の作成を求めます。このプロセスは必須であり、匿名モードはありません。初回起動後、他のユーザーに URL を共有する前に、直ちに owner を作成してください。docker compose up から最初のフォーム送信までの間は、最初にアクセスした者がインスタンスを占有できる状態になります。リバースプロキシによる Basic Auth の追加は、追加のセキュリティ策として有効ですが、あくまで二要素認証のようなものであり、本来の認証ではありません。
バックアップ: 最初に暗号化キー、次にデータベース
バックアップが必要な要素は2つありますが、その重要性は異なります。
N8N_ENCRYPTION_KEY。n8nに保存されるすべての認証情報(API tokens、database passwords、OAuth secrets)は、このキーによって保存時に暗号化されます。Postgres内のワークフローは、このキーがなければ機能しません。異なるキーを使用して新しいサーバーにデータベースを復元しても、n8nは認証情報を復号できません。復元やリセットは不可能です。.env ファイルにキーが保存されています。作成した当日に、サーバー以外の安全な場所(password-managerへの保存が最適)にコピーしてください。これが最も重要なバックアップです。
Postgres database。ワークフロー、実行履歴、および暗号化された認証情報自体を保存します。
docker compose exec -T postgres pg_dump -U n8n -d n8n \
| gzip > n8n-db-$(date +%F).sql.gzこれを定期的に実行し、ダンプファイルをサーバー外にコピーしてください。新しいVPSに復元する手順は以下の通りです。まずスタックを起動してデータベースを作成し、n8nを停止します。次に psql でダンプを読み込み、同じ N8N_ENCRYPTION_KEY を .env に設定して、n8nを起動します。同じキーとダンプがあれば、インスタンスは正常に動作します。新しいキーを使用した場合、ワークフローは復元できても、認証情報を一つも使用できません。
Upgrades: pin the tag
compose file では、意図的に latest ではなく n8nio/n8n:2.29.10 を指定しています。n8n はほぼ毎週マイナーアップデートをリリースします。その際、データベーススキーマや node の動作が変更されることがあります。そのため、latest を使用していると、自動プルによって起動時にデータベースのマイグレーションが実行される可能性があります。バージョンを固定してください。アップデートを行う前に release notes を確認してください。破壊的変更についてはそこに記載されています。慎重にアップグレードしてください。
docker compose exec -T postgres pg_dump -U n8n -d n8n | gzip > pre-upgrade.sql.gz
# edit the image tag in docker-compose.yml, then:
docker compose pull n8n
docker compose up -d n8n
docker compose logs -f n8nメジャーバージョンの更新時に、この注意点が最も重要になります。例えば、2.0 系ではデフォルトで N8N_BLOCK_ENV_ACCESS_IN_NODE が true に変更されました。そのため、process.env を読み取っていた Code node は、false に戻すまでアクセスできなくなります。また、同じリリースから settings file に対する厳格な権限設定が適用されるようになりました。メジャーバージョンを更新する前に、2.0 breaking-changes page を確認してください。n8n は起動時に必要なデータベースマイグレーションを自動的に実行します。そのため、アップグレード前の pg_dump は必須となります。認証情報は .env 内のキーで暗号化されており、データは Postgres に保存されているため、コンテナは破棄可能です。コンテナを置き換えることでアップグレードを行い、以前の tag を指定して dump をリストアすることでロールバックできます。
失敗パターンと表示される文字列
The requested webhook "POST hello" is not registered. ワークフローが Active でない webhook を呼び出した場合、またはテストパスを誰も待機していない状態で呼び出した場合に発生する 404 エラーです。テストパス (/webhook-test/...) は "Listen for test event" をクリックしている間のみ応答します。本番用パス (/webhook/...) はワークフローのトグルが ON の時のみ応答します。隣接する This webhook is not registered for GET requests. Did you mean to make a POST request? は、メソッドが正しくないことを意味します。ノードは POST を期待していますが、GET が送信されています。
Webhook URL に :5678 または localhost が表示される。 ノードは https://n8n.example.com:5678/webhook/... または http://localhost:5678/... を表示します。WEBHOOK_URL が未設定または誤っているため、n8n はパブリックなベース URL の代わりに N8N_HOST:N8N_PORT からアドレスを構築しました。WEBHOOK_URL=https://n8n.example.com/ を設定し、docker compose up -d を使用してコンテナを再作成すれば、ポートの問題は解消されます。
ブラウザでの There was a problem loading init data。 エディタは読み込まれましたが、自身のバックエンド API に到達できません。プロキシ経由の場合、原因のほとんどは N8N_HOST または WEBHOOK_URL の誤り、WebSocket Upgrade ヘッダーの欠落、あるいは N8N_PROTOCOL が接続方法と一致していないことです。4つのパブリック用変数の設定と、プロキシが Upgrade および Connection を転送しているかを確認してください。
ログに password authentication failed for user "n8n" が表示され、コンテナが再起動する。 n8n が送信するパスワードが、データベースの初期化時に使用したものと一致しません。注意点として、Postgres は 空の データディレクトリを初期化する時にのみ POSTGRES_PASSWORD を読み取ります。一度スタックを起動した後、.env の POSTGRES_PASSWORD を変更すると、既存の postgres_data ボリュームには古いパスワードが残ったままになります。元の値に戻すか、データを保持する必要がない場合は、postgres ボリュームを docker compose down して docker volume rm し、新しく起動してください。
起動時の EACCES: permission denied, open '/home/node/.n8n/config'。 n8n は node ユーザー (UID 1000) として実行されるため、設定ディレクトリに書き込みができません。これは、root 所有のホストフォルダ (./n8n_data:/home/node/.n8n) をバインドマウントしている場合に発生します。上記の名前付きボリュームを使用するか、バインドマウントを使用する場合は、事前に sudo chown -R 1000:1000 ./n8n_data を行ってください。
Permissions 0644 for n8n settings file /home/node/.n8n/config are too wide. Changing permissions to 0600.. n8n 2.x 以降、デフォルトで設定ファイルに 0600 が適用され、起動時に自動的に修正されます。このログ行は、すでにモードが修正されたことを意味します。これは、バインドマウント後や、復元によって権限が緩いファイルがコピーされた際によく発生します。対応は不要です。ファイルシステムが権限をサポートできない場合に限り、N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=false を設定してください。
Mismatching encryption keys — 詳細なメッセージには、設定ファイルの暗号化キー /home/node/.n8n/config が環境変数の N8N_ENCRYPTION_KEY と一致しないことが示されます。環境変数のキーが、以前の実行時に n8n がデータボリュームに書き込んだキーと異なっています。これは通常、変数が未設定の状態で起動した際に n8n がランダムなキーを生成し、その後に別のキーを設定したために起こります。元のキーを .env に戻すか、保持すべき認証情報がない場合に限り、n8n_data ボリューム内の config ファイルを削除して n8n に再生成させてください。その際、既存の認証情報は読み取れなくなります。
セキュアクッキーに関するログインバナー: Your n8n server is configured to use a secure cookie, however you are either visiting this via an insecure URL, or using Safari. N8N_PROTOCOL=https を設定していますが、HTTPS プロキシではなく IP とポートに直接アクセスしたため、プレーンな HTTP で n8n に到達しています。https://n8n.example.com/ を経由してアクセスしてください。どうしても HTTPS が使用できない場合に限り N8N_SECURE_COOKIE=false を設定してください。インターネットに公開するサーバーでは決して設定しないでください。
ワークフロー内に言語モデルを組み込むには、Claude と n8n を使用した AI ワークフローの構築 を参照してください。
FAQ
n8nにはSQLiteとPostgresのどちらを使うべきですか?
SQLite(デフォルト)は、n8nの試用や、一度に1つのワークフローのみを実行する個人用インスタンスに適しています。信頼性が求められる環境ではPostgresに移行してください。SQLiteは単一の書き込みロックを使用するため、並行処理時に database is locked が発生します。一方、Postgresは pg_dump によるクリーンなバックアップが可能です。後からの移行は手動作業になるため、本番環境として運用する場合は最初からPostgresを使用してください。
n8nのwebhookが実行されないのはなぜですか?
ほとんどの場合、原因は WEBHOOK_URL です。N8N_HOST:N8N_PORT から生成されたwebhookアドレスが未設定または誤っている場合に発生します。これには :5678 や localhost が含まれることが多く、一見正しく見えますが、インターネットから到達できないためリクエストが届きません。WEBHOOK_URL=https://n8n.example.com/ を設定し、ノードにポート番号を含まないURLが表示されていることを確認してください。もう一つの原因は、Activeに設定されていないワークフローのwebhookを呼び出していることです。この場合、The requested webhook ... is not registered. が返されます。
n8nで何をバックアップすべきですか?
2つの要素が必要です。1つ目は .env ファイルに含まれる N8N_ENCRYPTION_KEY です。保存されたすべての認証情報はこれによって暗号化されており、紛失すると二度と復号できません。作成した当日にサーバー外へコピーしてください。2つ目は、ワークフロー、履歴、認証情報を保持するPostgresデータベースの pg_dump です。復元には、このダンプファイルと同じキーの両方が必要です。
n8nをHTTPS化するにはどうすればよいですか?
n8nはポート5678でプレーンなHTTPを配信します。リバースプロキシを前段に配置してTLSを終端させてください。n8nを 127.0.0.1:5678 にバインドしてプロキシからのみアクセス可能にし、Traefik(自動証明書)またはnginx(Let's Encrypt証明書)を使用します。N8N_PROTOCOL=https と WEBHOOK_URL=https://your-host/ を設定してください。また、プロキシがWebSocketの Upgrade ヘッダーを転送するように設定しないと、エディタがフリーズします。
n8nを安全にアップグレードする方法は?
latest の代わりに特定のイメージタグを指定してください。n8nは起動時に自動的にマイグレーションを実行するため、まず pg_dump を取ってください。次にリリースノートで破壊的変更を確認し、タグを更新して docker compose pull n8n && docker compose up -d n8n を実行します。コンテナは破棄可能です。アップグレードに失敗した場合は、前のタグを指定して、アップグレード前のダンプを復元することでロールバックできます。