1分で読了連携

セルフホストn8nによるリンク自動化: DockerからWebhookまで

Elido webhook、Docker Compose、キューモードを使ってセルフホストn8nのリンク自動化を運用し、ワークフローのデータをEU内の自社管理インフラに置く方法。

Marius Voß
DevRel · edge infra
リンク向けセルフホストn8n自動化: Elidoのwebhookがリバースプロキシを経由し、キューとワーカーを持つn8nインスタンスに入る。すべてが自社管理インフラの中で完結している。

リンク向けのセルフホストn8n自動化とは、自社管理下のサーバー上で動く3つの要素を意味します。Docker上のn8nインスタンス、Elidoのwebhookイベントを受け取る公開HTTPSエンドポイント、そしてスコープ付きトークンを使ってElido APIに送り返すアウトバウンドの呼び出しです。リンクとドメインのイベントは署名付きで届き、クリック単位のイベントはロードマップに載っています。次に何をするかはあなたのワークフローが決め、実行ログが自社のインフラの外に出ることはありません。

アーキテクチャはこれで全部です。この記事の残りは、クイックスタートガイドが省略しがちな部分を扱います。リバースプロキシ越しにwebhookを届ける方法、見知らぬ第三者にワークフローを起動させないための署名確認方法、キューモードで何が変わるか、そして正直なところn8n Cloudの方がよい選択になる場合です。私はこの構成を1台の小さなVM上で運用しており、可動部分は1画面に収まります。

リンクそのものも自社のハードウェアに置きたい場合、それは別の、はるかに大きな作業になります。k3sでのElidoセルフホスティングの手引きがそれをカバーしています。ここでは、Elido自体はマネージドのまま、自動化レイヤーだけを社内に移します。

リンク自動化のためにn8nをセルフホストする理由

よくある理由はデータです。セルフホストのn8n URL短縮ワークフローは、処理するすべてのペイロードを目にします。遷移先URL、タグ、時には思っている以上のことを語るキャンペーン名、クリック分析を取り込んでいれば国、デバイス、リファラーもです。n8n Cloudでは、これらの実行結果は他社のサーバー上に、他社が決めた保持期間のデフォルト設定のもとで保存されます。セルフホストであれば、あなたが選んだリージョンの、あなたのPostgresの中に収まります。

GDPR第28条のもとでは、個人データに触れるすべての処理者について契約と記録が必要です。処理者が少なければ、書類作業も少なくて済みます。マーケティングデータをすでにEU内にとどめる必要がある場合は、EUデータレジデンシーガイドで、自動化レイヤーも短縮ツールと同じくらい重要である理由を解説しています。

2つ目の理由はコストの形です。n8n Cloudは実行回数に応じた課金であり、頻繁なwebhookトリガーや高頻度の分析ポーリングはあっという間に実行回数を消費します。セルフホストであれば、1回の実行はテーブルの1行と数ミリ秒のCPU時間でしかありません。

3つ目は到達範囲です。セルフホストインスタンスは、あなたのCRMデータベースや社内チケットツールと同じネットワーク上に存在するため、リンクイベントを、本来インターネットに面することを想定していないシステムにまで届けることができます。

Docker Composeスタック

最小限のn8n Docker型リンク自動化スタックには4つのサービスが必要です。n8n自身のDocker Composeガイドが基準となる資料ですが、これはリンク作業向けに私が最初に使う構成で、後で移行しなくて済むよう最初からキューモードを有効にしています。

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: n8n
      POSTGRES_PASSWORD: ${PG_PASSWORD}
      POSTGRES_DB: n8n
    volumes: [pgdata:/var/lib/postgresql/data]

  redis:
    image: redis:7

  n8n:
    image: docker.n8n.io/n8nio/n8n
    env_file: .env.n8n
    ports: ["127.0.0.1:5678:5678"]
    depends_on: [postgres, redis]

  n8n-worker:
    image: docker.n8n.io/n8nio/n8n
    command: worker
    env_file: .env.n8n
    depends_on: [n8n]

volumes:
  pgdata:

そして共有の環境変数ファイルです。

DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=change-me
EXECUTIONS_MODE=queue
QUEUE_BULL_REDIS_HOST=redis
N8N_ENCRYPTION_KEY=generate-a-long-random-string
N8N_WEBHOOK_URL=https://n8n.example.com/
N8N_PROXY_HOPS=1

見た目以上に重要な行が2つあります。暗号化キーはメインプロセスとすべてのワーカーで同一でなければなりません。そうでなければワーカーはElidoの認証情報を復号できず、すべての実行がわかりにくい認証エラーで失敗します。また、ポート5678はlocalhostにのみバインドされています。インターネットに面するべきものはリバースプロキシだけだからです。

リンク自動化向けのセルフホストn8nアーキテクチャ: Elidoが署名付きwebhookをHTTPS経由でリバースプロキシに送信し、それがn8nのメインプロセスに転送され、実行がRedisにキューイングされ、Postgresを使うワーカーが処理する。ワーカーはスコープ付きトークンでElido APIをアウトバウンドに呼び出す。

セルフホストのn8nからElido APIを呼び出す

アウトバウンドの呼び出しには、n8n標準の機能以上のものは何も必要ありません。名前をAuthorization、値をBearer に続けてElidoダッシュボードのAPIキーとしたHeader Auth認証情報を作成し、組み込みのHTTP Requestノードから使ってください。n8nは環境変数ファイルのキーを使ってこれを保存時に暗号化します。これも、そのキーをあらゆる場所で一致させておく必要がある理由のひとつです。

すべてのリンクルートはワークスペースにスコープされています。URLを短縮するには、https://api.elido.app/v1/workspaces/{workspace_id}/linksにJSONボディを添えてPOSTしてください。

{
  "domain_id": 12,
  "destination_url": "{{ $json.url }}",
  "title": "Spring launch",
  "tags": ["n8n", "spring"]
}

slugを省略すればElidoが自動生成します。domain_idはそのリンクが属する短縮ドメインで、/v1/workspaces/{workspace_id}/domainsへのGETで一覧を取得できます。私であれば、実行のたびに検索するのではなく、ワークフロー内にそのIDをハードコードします。同じlinksルートへのGETでリンクを一覧表示でき、PATCH /v1/workspaces/{workspace_id}/links/{link_id}で後から遷移先やタグを変更できます。

POSTにはIdempotency-Keyヘッダーを設定してください。トリガーとなった項目の中の、行IDのような安定した値から組み立てます。タイムアウト後にn8nがノードをリトライした場合、APIは2つ目のリンクを作成する代わりに最初のレスポンスを再生します。API・SDKクイックスタートが残りの仕様をカバーしており、後で一部をコードに移したくなった場合はAPI・SDKページも参考になります。

これらの呼び出しをより扱いやすい形にラップするパッケージ化されたコミュニティノードn8n-nodes-elidoもあります。npmでバージョン0.2.0として公開されていますが、依然としてオプションです。検証済みノードではないためセルフホストn8nにのみインストールされ、この投稿ではその構成を前提としています。HTTP Request版を基準として保っておいてください。キューモードでコミュニティパッケージをインストールする場合、GUIはメインコンテナにのみインストールされ、ワーカーには反映されない点に注意してください。n8n 2.21以降では、環境変数によるインストールの方法で、起動時にすべてのコンテナを揃えることでこれを解決していますが、リストにないものは最初の起動時にアンインストールされます。

リバースプロキシ越しにElidoのwebhookを届ける

イベントは逆方向にも流れます。Elidoはlink.createdlink.updateddomain.verified(ほか)を、Settings、Webhooks配下で登録したエンドポイントに発行します。セルフホストのn8nでは、そのエンドポイントはWebhookノードの本番URLになります。クリック単位のclick.createdイベントはロードマップに載っていますがまだ出荷されていないため、現時点でのクリックデータは分析APIのスケジュール実行によるプルから取得します。イベントの全リストとペイロードの形式はリンクイベント向けWebhookのリファレンスにあります。

プロキシの背後では、n8nは自身のプロトコル、ホスト、ポートからwebhook URLを組み立てるため、聞かれれば誰にでもhttp://localhost:5678/webhook/...を平気で公開してしまいます。リバースプロキシ設定のページは短く、全文読む価値があります。N8N_WEBHOOK_URLを公開アドレスに設定し、N8N_PROXY_HOPS=1を設定し、最終段のプロキシにX-Forwarded-ForX-Forwarded-HostX-Forwarded-Protoを転送させてください。古いガイドにはWEBHOOK_URLと書かれていますが、現行リリースでもそれは読み込まれるものの、非推奨の警告がログに出ます。

Nginx、Traefik、すでに使っているものであれば何でも構いません。重要なのは、公開側でのTLSと、/webhook/*というパスが手を加えられずにn8nまで届くことです。

私がはまったタイミング上の落とし穴がひとつあります。Elidoは、配信を失敗とみなすまでレスポンスを10秒待ちます。遅いスプレッドシートAPIへの書き込みを行ってからレスポンスを返すワークフローは、この時間を超えてしまい、リトライされ、同じ行を2回書き込んでしまうことがあります。Webhookノードは即座にレスポンスを返し、実際の処理はそのあとで行うように設定してください。

これをクライアント向けに構築していて、webhook側の対応をこちらに任せたい場合は、webhook機能ページで、何も構築する前に購読できる内容を確認できます。

何かを実行する前に署名を検証する

公開されたwebhook URLは、公開されたURLです。それを見つけた人は誰でも偽のイベントをPOSTしてワークフローを起動できるため、トリガーの後の最初のノードは署名の確認であるべきです。

Elidoの各配信には、X-Webhook-Signature(値はv1=に16進ダイジェストを続けたもの)、Unix秒でのX-Webhook-TimestampX-Webhook-EventX-Webhook-Deliveryが含まれます。このダイジェストは、エンドポイント作成時に一度だけ表示されるwhsec_シークレットをキーとして、タイムスタンプ、ドット、生のリクエストボディに対して計算されたHMAC-SHA256です。

まずWebhookノードでRaw Bodyを有効にしてください。これは多くの人が見落とすステップです。Elidoが送った正確なバイト列ではなくJSON.stringify($json.body)をハッシュ化してしまうと、キーの順序や空白の違いによってすべての署名が失敗し、シークレットが間違っていると思い込んだまま一晩を費やすことになります。その後、Codeノードを次のように設定します。

const crypto = require("crypto");
const item = $input.first();
const h = item.json.headers;
const raw = (await this.helpers.getBinaryDataBuffer(0, "data")).toString(
  "utf8",
);

const ts = Number(h["x-webhook-timestamp"]);
if (Math.abs(Date.now() / 1000 - ts) > 300) throw new Error("stale delivery");

const expected =
  "v1=" +
  crypto
    .createHmac("sha256", $env.ELIDO_WEBHOOK_SECRET)
    .update(`${ts}.${raw}`)
    .digest("hex");
const got = h["x-webhook-signature"] || "";
if (
  got.length !== expected.length ||
  !crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))
) {
  throw new Error("bad signature");
}
return [{ json: JSON.parse(raw) }];

cryptoモジュールはCodeノードでデフォルトで利用できるため、追加の設定は不要です。5分の許容幅が、捕捉されたリクエストのリプレイを防ぎます。シークレットをローテーションする際、Elidoは猶予期間中X-Elido-Signature-Previousも送信するため、変数を更新している間はどちらのキーでも受け付けることができます。Webhook署名の検証ガイドには、両方のヘッダーを確認するこのノードのバージョンに加えて、Node、Python、Goで同じ確認を行う例があります。

セルフホストn8nでのwebhook署名検証: タイムスタンプと生のボディを読み取り、300秒より古い配信は拒否し、エンドポイントのシークレットを使ってタイムスタンプ.生のボディに対するHMAC-SHA256を再計算し、定数時間で比較し、その後ワークフローを実行するか実行を停止するかに分かれる。

キューモードとリトライ

署名の確認ができたら、残る問題は負荷がかかったときや何かがダウンしているときに何が起きるかです。ここには2つのリトライの仕組みが関わっており、それぞれ異なる種類の失敗をカバーします。

n8nのキューモードでは、メインプロセスがwebhookを受信して実行を作成し、そのIDをRedisに渡します。ワーカーがそれを取り出します。イベントのバーストは、HTTPレスポンスを詰まらせる代わりにキューに積み上がります。受信量が多い場合は、ロードバランサーの背後に専用のwebhookプロセッサーを追加できますが、ほとんどのリンク関連ワークロードでは、メインプロセス1つとワーカー2つで十分です。

Elido側は、n8nに到達できない状態を処理します。2xx以外のレスポンスやタイムアウトは、1分後、5分後、15分後にリトライされます。3回試行した後、その配信は失敗としてマークされ、ダッシュボードから再起動できます。これはコンテナの再起動や短時間のデプロイをカバーしますが、週末をまたぐ障害まではカバーしません。だからこそ私は、webhook対ポーリングの記事が主張する通り、webhookとAPI経由でリンク一覧を取得する夜間の突き合わせ処理を組み合わせることをお勧めします。

X-Webhook-Deliveryを冪等性キーとして使ってください。リトライでもこの値は再利用されます。

セルフホストn8n対n8n Cloud: 正直なトレードオフ

私自身はこの用途にはセルフホストを好みますが、それを後悔しているチームも見てきました。どちらの側の売り込みでもない、率直な比較を示します。

観点セルフホストn8nn8n Cloud
実行データの所在自社サーバー、自社リージョン、自社の保持設定n8nのインフラとそのデフォルト設定
社内システムへの到達性CRMやデータベースと同じネットワーク自分が公開した範囲のみ
公開webhook URLとTLS自分でプロキシと証明書を運用提供済み
アップグレード・バックアップ・パッチ適用毎月自分の仕事対応済み
イベント量が多いときのコストサーバーの定額コスト実行回数に応じてスケール

最後の行はどちらの方向にも働きます。小さなVMは安価ですが、壊れたアップグレードに費やすエンジニアの1時間は安くはなく、n8nは頻繁にリリースされます。誰もその箱の面倒を見る人がチームにいないなら、HTTP RequestノードとElidoのREST APIを使ったCloudの方が理にかなった選択であり、MakeとIFTTTのレシピZapierガイドが、他のプラットフォームでのそのマネージドな経路がどう見えるかを示しています。

私にはお答えできないのは、あなたの会社のデータ保護責任者が、ベンダーとのDPAよりもセルフホストの箱の方をシンプルだと受け入れてくれるかどうかです。私の経験では、たいていは受け入れてもらえますが、それはその箱がどれだけきちんと運用されているかに左右されます。短縮ツール側の契約がどう機能するかについては、URL短縮ツールのためのGDPRガイドから読み始めるとよいでしょう。そして、最初のワークフローを組む準備ができたら、ワークスペースでAPIトークンを取得し、Webhookノードをそこに向けてください。

関連記事

よくある質問

セルフホストのn8nでURL短縮ツールを使えますか?

使えます。セルフホストのn8nは、組み込みのHTTP Requestノードを通じて、REST APIを持つあらゆる短縮ツールを呼び出せます。Elidoの場合、APIキーを持つHeader Auth認証情報と、ワークスペーススコープのlinksルートへのPOSTがそれにあたります。link.createdのような受信イベントは、n8n組み込みのWebhookノードを通じて届き、公開されたHTTPS URLが必要です。クリック単位のclick.createdイベントは計画されていますが、まだ利用できません。

セルフホストのn8nにコミュニティノードをインストールするにはどうすればいいですか?

単一インスタンスであれば、Settings、次にCommunity Nodesを開き、npmパッケージ名を貼り付けてください。キューモードでは、GUIからのインストールはワーカーには反映されないため、各コンテナ内にパッケージをインストールするか、n8n 2.21以降ではN8N_COMMUNITY_PACKAGES_MANAGED_BY_ENVをtrueに設定したうえでN8N_COMMUNITY_PACKAGESに列挙してください。Elidoについてはコミュニティノードは不要で、HTTP RequestノードでAPI全体をカバーできます。

n8nのWEBHOOK_URLとは何ですか?

n8nが、エディター上に表示し外部サービスに登録すべき公開アドレスを示す設定です。リバースプロキシの背後では、n8n自身のホスト名とポートからそれを推測できないためです。現行のn8nはN8N_WEBHOOK_URLを読み込み、古いWEBHOOK_URLについては非推奨の警告をログに出します。これをN8N_PROXY_HOPS=1とプロキシ側でのヘッダー転送と組み合わせて使ってください。

n8nでwebhookの署名を検証するにはどうすればいいですか?

WebhookノードでRaw Bodyオプションを有効にし、タイムスタンプヘッダー、ドット、生のボディに対して、エンドポイントのシークレットを使ってHMAC-SHA256を再計算するCodeノードを追加してください。署名ヘッダーと定数時間で比較し、5分より古いものは拒否します。再シリアライズされたJSONではなく、必ず生のバイト列に対して検証してください。

n8nのキューモードにはRedisが必要ですか?

必要です。キューモードでは、メインインスタンスとwebhookプロセッサーが受信したトリガーを実行IDに変換し、Redisを使ったキューに積み、ワーカーがそこから取り出します。n8nはSQLiteでのキューモード利用も推奨していないため、Postgresを前提に計画してください。すべてのプロセスは同じ暗号化キーを共有する必要があります。そうでなければワーカーは保存済みの認証情報を復号できません。

セルフホストのn8nはn8n CloudよりGDPRの観点で有利ですか?

有利になり得ます。ワークフローのデータ、実行ログ、認証情報が物理的にどこに存在し誰が管理するかを自分で選べるためです。ただし自動的に準拠するわけではなく、パッチ適用、アクセス制御、バックアップ、保持期間の管理はすべて自分の責任になります。クリックデータを扱うリンク自動化であれば、EUリージョンでのセルフホストにより記録から処理者を1つ減らせるため、書類作業は簡単になります。

Elidoを試す

URLを貼り付けて短縮リンクを取得

登録不要。リンクは30日間有効。永久に保存するには登録してください。

Free、登録不要 · 1日あたり2件

Elidoを試す

EUホスティングのURL短縮サービス。カスタムドメイン、詳細な分析、オープンAPI付き。無料プラン - クレジットカード不要。

タグ
self-hosted n8n automation links
n8n self hosted url shortener
n8n docker link automation
n8n webhook signature verification
n8n queue mode
n8n http request node api

続きを読む