Nango をセルフホストして GitHub をつないだ——1,007 の API に対応する連携基盤で、無料でできること、deploy が通らない理由、freee が無い問題

SaaS 連携の認可とトークン管理を引き受ける Nango を Docker で立ち上げ、GitHub をつないで処理を 1 本動かした。無料でできるのは認可の保管と API の中継まで。定期実行は有料、freee は未収録、ドキュメントの手順はそのままでは動かない。自前実装との損益分岐と、導入の順番まで。

受託でも自社開発でも、外部の SaaS とつなぐ処理を書くたびに同じことを繰り返します。認可画面を出し、返ってきたトークンを暗号化して保存し、期限が切れる前に更新し、API の呼び出し回数の上限に当たったら待って再試行する。中身の処理より、この周辺のほうが手間も事故も多い。

Nango は、その周辺を丸ごと引き受ける連携基盤です。1,000 を超える API の認可とトークン管理、利用者ごとの API 呼び出しの中継、TypeScript で書いた同期処理の実行までを担います。この記事は、GitHub のリポジトリから手元の Mac に Docker で立ち上げ、GitHub をつないで実際にデータを取り、処理を 1 本書いて動かすところまでを記録したものです。検証日は 2026 年 9 月 14 日、サーバーは 0.71.7、対象コミットは 8ed88bc です。

読み手として想定しているのは、freee、kintone、Slack、Google Workspace との連携を何度も作ってきた開発者と、その工数を減らしたい会社の担当者です。API そのものの考え方はAI エージェント時代の API の記事に、ワークフロー型の自動化ツールとの比較はn8n と Zapier と Make の比較記事にあります。

Nango は Docker で 17 秒で立ち上がり、既存のアクセストークンを取り込む経路なら、ブラウザでの OAuth アプリ登録なしに GitHub のデータを取れた。TypeScript で書いた処理はローカルの dryrun で動いたが、無料のセルフホストでは deploy が通らない。無料で使えるのは認可の保管と API の中継までで、定期同期や Webhook は有料版が要る。対応 API は 1,007 件で kintone と Slack と Google Drive はあるが freee はない。ライセンスは Elastic License 2.0 で、自社製品の中で使うのは問題ないが、連携基盤そのものを他社に売ることはできない。

この記事の要約

Nango とは何か

README は 3 つの部品で説明しています。Auth は、1,000 以上の API について OAuth の画面遷移、API キーの保管、トークンの自動更新を代行し、自社アプリに埋め込める認可画面(Connect UI)も付きます。Proxy は、保管した資格情報を使って利用者ごとに外部 API を呼び、再試行と呼び出し回数の上限の扱いを含みます。Functions は、TypeScript で書いた同期処理と実行処理を Nango 側の実行環境で動かします。

自社で持たずに済むのは、利用者ごとのトークンをどこにどう暗号化して置き、いつ更新するか、という部分です。README は本番採用企業として Replit、Ramp、Mercor を挙げています。提供形態は、クラウド版と、機能を絞った無料のセルフホスト版、全機能のエンタープライズ向けセルフホスト版の 3 つです。

ドキュメントは対応 API を 1,000 以上と書いていますが、リポジトリ内の定義ファイルを数えると 1,007 件でした。認証方式の内訳も同じファイルから機械的に数えられます。1,007 件のうち 945 件に方式の記載があり、残り 62 件は他の定義を引き継ぐ別名です。

認証方式件数意味
API_KEY329API キーを渡すだけ。OAuth の画面遷移がない
OAUTH2302利用者が認可画面で許可する方式。Slack や Google など
OAUTH2_CC103OAuth 2.0 のクライアント資格情報方式。サーバー間
BASIC98ID とパスワード
TWO_STEP652 段階でトークンを得る独自方式
MCP_OAUTH228MCP サーバー向けの OAuth
その他の方式20OAUTH1 4、JWT 4、NONE 2 など 11 種
方式の記載なし62他の定義を引き継ぐ別名(Google カレンダーなど)が中心

API キー方式が最多で、OAuth を使わずにつなげる相手のほうが数の上では多い。日本の SaaS の収録状況は、記事の後半で扱います。

セルフホストは 17 秒で立ち上がる。ただしドキュメントの手順は使えない

公式のセルフホストの案内は、docker-compose.yaml を wget で取得して docker compose up するだけ、と書いています。これは今の compose ファイルとかみ合っていません。取得したファイルには、対応 API の定義ファイル(packages/providers/providers.yaml)をコンテナに割り当てる記述があり、wget しただけのディレクトリにはそのファイルが存在しないからです。割り当て元のファイルが存在しないことは docker compose config と ls で確認しました。この状態で起動すると Docker は割り当て先に空のディレクトリを作るため、対応 API の定義が読めません(起動そのものは今回試していません)。

したがって、リポジトリごと clone するのが正解です。clone は 15.7 秒、取得後の容量は 495 MB、イメージ取得込みの初回起動は 16.6 秒でした。回線とキャッシュの状況で変わります。

bash
git clone --depth 1 https://github.com/NangoHQ/nango.git repo
cd repo
cp .env.example .env   # NANGO_ENCRYPTION_KEY などを設定
docker compose -p nango up -d
curl -s http://localhost:3003/health
# {"result":"ok"}
実際に流したコマンド。管理画面は 3003 番、利用者向けの認可画面は 3009 番

今回の Mac では PostgreSQL の 5432 番と Redis の 6379 番が別のコンテナで使用中だったため、上書き用の compose ファイルで別の番号に逃がしました。同時に、既定では無効になっているログの保管(Elasticsearch)を有効にしています。イメージの容量は、Nango 本体が 1.4 GB、Elasticsearch が 1.26 GB、PostgreSQL が 348 MB、Redis が 198 MB の合計約 3.2 GB です。Elasticsearch を使わなければ約 2.0 GB で済みます。

Nango の構成図。左に自社アプリと利用者、中央に Nango サーバー(Auth・Proxy・Functions)、右に GitHub や Slack などの外部 API。下に PostgreSQL、Redis、Elasticsearch。無料セルフホストで使えるのは Auth と Proxy で、Functions の定期実行はクラウドかエンタープライズ版が要る。
作成 はてなベース株式会社。README とセルフホストの案内、手元で起動したコンテナ構成(2026 年 9 月 14 日時点)に基づく

起動でつまずいたのは 3 か所です。1 つ目、前回の試行で起動途中のコンテナを落としていたため、データベースのマイグレーション(構造の更新処理)のロック行が残り、起動を繰り返す状態になりました。データベースを作り直さずに、ロック表の該当行を 0 に戻すと直ります。ロック表は 3 つのスキーマに分かれているので、直前のログでどれか特定します。

bash
docker exec nango-db psql -U nango -d nango 
  -c "UPDATE nango_records.migrations_lock SET is_locked = 0;"
docker compose -p nango restart nango-server
「Migration table is already locked」で起動を繰り返すときの解除。表名はログに出たスキーマに合わせる

2 つ目、環境変数 SERVER_PORT を書かないと 3003 番で待ち受けません。compose のポート公開は既定値 3003 が効くのに、コンテナ内のサーバーは環境変数が空だと 8080 番で起動します。既定値の食い違いで、3003 番へ接続してもエラーメッセージが出ないまま応答がありません。起動ログの listening on port 8080 を見ないと気づけません。.env.example を写して使えば踏みません。3 つ目、ログを有効にする設定だけでは起動せず、Elasticsearch のユーザー名とパスワードの環境変数が、値は空でよいのですが、キー自体が無いと起動しません。

ブラウザなしで GitHub をつなぎ、データを取る

通常の経路では、相手先の開発者コンソールで OAuth アプリを登録し、そのクライアント ID とシークレットを Nango に入れて、利用者に認可画面で許可してもらいます。今回はその手順を避け、認可の案内にある「手持ちの資格情報で API から接続を作る」経路を使いました。GitHub の CLI が持っているアクセストークンを、そのまま Nango に取り込みます。

最初に環境のシークレットキーが要ります。管理画面の設定ページには表示されず、管理画面の API から取ります。既定で全権限のキーが 1 本作られています。次に連携先(integration)を作ります。GitHub の定義は OAuth 2.0 なので、クライアント ID とシークレットの入力が必須ですが、トークンを直接取り込む今回の経路では使われないため、仮の値を入れました。

bash
curl -s -X POST http://localhost:3003/integrations 
  -H "Authorization: Bearer $NANGO_SECRET_KEY" -H 'Content-Type: application/json' 
  -d '{"provider":"github","unique_key":"github-pat","display_name":"GitHub (PAT import)",
       "credentials":{"type":"OAUTH2","client_id":"unused-placeholder",
                      "client_secret":"unused-placeholder","scopes":"repo,read:user"}}'

GH=$(gh auth token)
curl -s -X POST http://localhost:3003/connections 
  -H "Authorization: Bearer $NANGO_SECRET_KEY" -H 'Content-Type: application/json' 
  -d "{"provider_config_key":"github-pat","connection_id":"koji-github",
       "credentials":{"type":"OAUTH2","access_token":"$GH"},
       "end_user":{"id":"koji","email":"koji@example.com","display_name":"Koji"}}"
連携先の登録と、既存トークンを取り込んだ接続の作成。ブラウザ操作は一切していない

接続ができたら、Proxyで外部 API を呼びます。呼び出し側は api.github.com を知りません。接続 ID と連携先 ID だけを渡し、トークンはサーバー側で解決されます。これが Proxy の要点です。

bash
curl -s "http://localhost:3003/proxy/user/repos?per_page=3&sort=updated" 
  -H "Authorization: Bearer $NANGO_SECRET_KEY" 
  -H "Connection-Id: koji-github" -H "Provider-Config-Key: github-pat"
# [{"full_name":"...","private":true,"updated_at":"2026-09-14T22:24:52Z"}, ...]
Proxy 経由で GitHub のリポジトリ一覧を取る。応答は GitHub の API がそのまま返る
Nango の管理画面の Integrations ページ。左にメニュー(Getting started、Integrations、Connections、Logs、Metrics、Environment settings)、中央の一覧に GitHub (PAT import) が 1 件、ID は github-pat、接続数 1、Auth Type は OAuth 2 と表示されている。
連携先の一覧。GitHub を OAuth 2 の定義で登録し、接続を 1 件作った状態
Nango の接続の詳細ページ。GitHub (PAT import) x Koji の見出し、Auth タブに end_user_id と end_user_email と end_user_display_name のタグ、伏せ字のアクセストークン、空の接続設定とメタデータ、右の欄に Connection ID koji-github、Auth type OAuth 2、作成日時が表示されている。
接続の詳細。取り込んだアクセストークンは伏せ字で表示される

なお、Nango が用意した共用の OAuth アプリを使う quickstart の経路は、セルフホストでは使えません。API が「この provider には Nango 提供の開発者アプリが設定されていない」と返します。通常の経路で必要になる作業は、相手先で OAuth アプリを登録し、コールバック URL に Nango のもの(今回の構成なら localhost:3003/oauth/callback)を登録し、発行されたクライアント ID とシークレットを連携先に入れ、接続作成の APIかセッションを作って利用者に認可画面で許可してもらう、の 4 段階です。kintone だけは接続時にサブドメインの指定が要ります。

TypeScript で処理を書き、dryrun で動かす

CLIで雛形を作り、処理を 1 本足しました。雛形は GitHub 用の同期処理、実行処理、イベント処理が 1 本ずつ入ります。今回書いたのは、つないだ GitHub の利用者が見られるリポジトリを新しい順に返す実行処理(action)です。連携先 ID と同じ名前のディレクトリに置きます。

typescript
import { createAction } from 'nango';
import * as z from 'zod';

const repoSchema = z.object({
    full_name: z.string(), private: z.boolean(), stars: z.number(), updated_at: z.string()
});

const action = createAction({
    description: 'List the repositories the connected GitHub user can see (newest first).',
    version: '1.0.0',
    endpoint: { method: 'GET', path: '/github-pat/repos', group: 'Repos' },
    input: z.object({ per_page: z.number().default(5) }),
    output: z.object({ count: z.number(), repos: z.array(repoSchema) }),
    exec: async (nango, input) => {
        const res = await nango.proxy<any[]>({
            method: 'GET', endpoint: '/user/repos',
            params: { per_page: String(input.per_page), sort: 'updated' }
        });
        const repos = res.data.map((r) => ({
            full_name: r.full_name, private: r.private, stars: r.stargazers_count, updated_at: r.updated_at
        }));
        await nango.log(`Fetched ${repos.length} repositories`);
        return { count: repos.length, repos };
    }
});
export default action;
github-pat/actions/listRepos.ts。入力と出力の型を zod で宣言し、exec の中で Proxy を呼ぶ
bash
export NODENV_VERSION=22.18.0
npm install nango@latest
./node_modules/.bin/nango init proj --auto-confirm
./node_modules/.bin/nango dryrun listRepos koji-github 
  --integration-id github-pat --input '{"per_page":5}' 
  -e dev --no-interactive --auto-confirm
# ✓ Typechecking
# ✓ Compiled
# http [200] GET http://localhost:3003/proxy/user/repos?per_page=5&sort=updated
# Fetched 5 repositories
# { "count": 5, "repos": [ ... ] }
dryrun の実行。処理はローカルの Node で動き、資格情報と API 呼び出しだけがセルフホストのサーバーを経由する

dryrun は --auto-confirm だけでは環境選択の質問で止まります。-e dev と --no-interactive を付けます。ここまでは無料のセルフホストで問題なく動きました。

Nango の管理画面の Logs ページ。6 件のログが並び、いずれも Status が SUCCESS、Type が PROXY、Integration が github-pat、Connection が koji-github。Duration は 245 ミリ秒から 457 ミリ秒。
Proxy 経由の呼び出しはすべてログに残る。所要は 245〜457 ミリ秒

deploy は無料セルフホストでは通らない

dryrun は動きますが、nango deploy は失敗します。処理の実体を S3 に置きに行き、AWS の資格情報がないので落ちます。無料セルフホストの構成には S3 の設定が含まれていません。公式の「無料セルフホストは Auth と Proxy のみ」という記載と符合します。

plain
./node_modules/.bin/nango deploy dev --auto-confirm --no-interactive
# ✗ Deploying 1 functions
# Error deploying:
# Error uploading file. Please contact support with the filename and connection details (file_upload_error)
#
# サーバー側のログ
# error (err) Could not load credentials from any providers { err: { CredentialsProviderError: ... } }
deploy の失敗。処理の実体を S3 に置く前提の構成で、無料セルフホストにはその設定がない

要点はここです。無料セルフホストで使えるのは、認可の保管と Proxy 経由の API 呼び出しまで。定期同期、Webhook、MCP サーバーはクラウド版かエンタープライズ版のセルフホストが要ります。「書いて試す」ところまでは無料でできて、「定期実行させる」ところから有料になる、と理解するのが正確です。

もう 1 つ、雛形の github ディレクトリを消さずに deploy すると、その前に別のエラーで止まります。ディレクトリ名が、管理画面に登録した連携先 ID と一致していないと通りません。

日本の SaaS はどこまで入っているか

当社が受託と自社開発で繰り返し作っている連携先を、定義ファイルで直接検索しました。公式のカタログでも同じことが確認できます。

サービス収録認証方式備考
freeeなし会計、人事労務とも未収録。検索結果は 0 件
kintoneありOAuth 2.0接続時にサブドメインを指定する(個別ページ
SlackありOAuth 2.0
Google DriveありOAuth 2.0Google 共通の定義を継承する
GitHubありOAuth 2.0、GitHub App個人アクセストークン専用の定義はない。今回は OAuth 2.0 の定義にトークンを取り込んだ

freee をつなぐなら、自分で定義ファイルに追加して本家に提案するか、自分のフォークで持つことになります。ここは正直に書いておくべき落差です。国内の会計 SaaS を軸にした連携が主目的なら、Nango を入れる前に、この定義を誰が書いて誰が保守するかを先に決めます。SaaS に振り回されない働き方の記事で書いたツール横断の考え方は、この定義があって初めて成り立ちます。

ライセンスと料金

ライセンスは Elastic License 2.0 で、OSI が承認したオープンソースライセンスではありません。本文から、実務で効く制限は 3 つです。

制限実務での意味
ホスティング提供の禁止Nango を載せた連携基盤を、他社向けのサービスとして提供できない。自社製品の中で使うのは問題ない
ライセンスキー機構の迂回禁止有料機能の鍵を外して使うことはできない
表示の削除禁止著作権表示を消せない。改変した場合はその旨を明示する義務は別条項(Notices)にある

違反すると許諾が自動的に終了します。通知から 30 日以内に是正すれば遡って復活しますが、再度違反した場合は永久に終了します。判断の目安は、自社の製品が外部 SaaS とつながるために使うなら問題なく、連携基盤そのものを他社に売るならライセンス違反になり得る、です。

クラウド版の料金は公式の料金ページの記載です。金額は 2026 年 9 月 14 日時点で、いずれも米ドルです。

プラン月額含まれるもの
Free0 ドル接続 10 件、実行時間 10 時間、転送 10 GB。認証、事前構築済みの連携部品、Webhook、UI 部品、ホワイトラベル、ログ。SOC 2 Type II
Pay-as-you-go50 ドル(クレジットとして消費)環境無制限、権限管理、ログの外部出力、SAML SSO。従量は接続 0.29 ドル、実行時間 1 時間あたり 0.72 ドル、転送 1 GB あたり 0.5 ドル
Growth(追加)450 ドル新規 API の対応 5 営業日。専用の Slack Connect チャンネルは Pay-as-you-go 以上に付く
Enterprise個別見積自社クラウドでのセルフホスト、HIPAA、SLA、専任のソリューションエンジニア。規模によっては接続 1 件あたり月 0.01 ドルまで下がる

無料セルフホストは、接続 10 件のような数量の制限ではなく、機能そのものが Auth と Proxy に限られます。比較するなら、接続 10 件だが全機能が使えるクラウドの Free との二択です。連携の費用を先に見積もる考え方はAI コスト爆発とデータ統合の記事に書きました。

詰まりどころの一覧

wget の件を除き、すべて実機で踏んだものです。

事象原因対処
wget だけで起動すると対応 API の定義が読めないcompose がリポジトリ内のファイルを割り当てる前提。wget ではそのファイルが取れないリポジトリを clone して、その中で docker compose up する
3003 番に接続できないSERVER_PORT が空だとコンテナ内のサーバーは 8080 番で起動する.env.example を写して SERVER_PORT=3003 を明示する
Migration table is already locked で起動を繰り返す起動途中でコンテナを落とし、ロック行が残ったロック表の該当行を 0 に戻して再起動する。ロック表はスキーマごとに 3 つ
ログ画面が Logs not configured のままElasticsearch が compose でコメントアウトされている。有効化しても認証情報のキーが必須Elasticsearch を足し、NANGO_LOGS_ES_USER と NANGO_LOGS_ES_PWD を空の値で書く。容量が約 1.2 GB 増える
nango deploy が file_upload_error で止まる処理の実体を S3 に置く構成で、無料セルフホストにはその設定がないdryrun で動作確認まで。定期実行はクラウド版かエンタープライズ版
ドキュメントの URL が 404docs.nango.dev から nango.dev/docs へ移転し、転送先も一部 404全ページの索引 llms.txt で正しい URL を引く

自前実装と比べて、どこで得をするか

得をするのは、つなぐ相手が多く、利用者ごとにトークンを持つ場合です。自社製品の利用者それぞれが自分の Slack や Google Drive をつなぐ、という形なら、認可画面、トークンの暗号化保存、期限切れ前の更新、呼び出し回数の上限の扱いを Nango に任せられます。利用者数が数十を超えたあたりから、自前で持つコストとの差が出ます。MCP 経由でつなぐ道具との使い分けはClaude の MCP コネクタの記事で整理しています。

得をしないのは、つなぐ相手が 1〜2 種類で、トークンが会社に 1 本しかない場合です。社内の kintone と Slack を会社のアカウントでつなぐだけなら、Nango のサーバーを 1 台運用する手間のほうが大きい。その用途なら、ワークフロー型の自動化ツールのほうが向きます。

運用の負荷は、PostgreSQL、Redis、必要なら Elasticsearch を自社で持つ、という点に集約されます。n8n を VPS でセルフホストした記事と同じ考え方で、バックアップと更新の手順を先に決めてから入れます。今回の検証で確かめられなかったのは、ブラウザでの OAuth 認可の実際の画面遷移、定期同期の実運用、トークンの自動更新(取り込んだトークンに期限がなく更新が走らないため)、クラウド版の挙動、freee 連携の 5 つです。

導入を検討するなら、この順番で

  1. つなぐ相手を書き出し、定義ファイルにあるか、認証方式は何かを確認する。freee のように無い相手があれば、定義を誰が書くかを決める
  2. 利用者ごとにトークンを持つか、会社に 1 本かを決める。1 本ならワークフロー型のツールも候補に入れる
  3. リポジトリを clone してセルフホストを立ち上げ、手持ちのトークンを取り込んで Proxy でデータが取れるところまで確かめる
  4. 処理を 1 本書いて dryrun で動かす。ここまでは無料
  5. 定期実行が要るなら、クラウドの Free(接続 10 件)で試し、費用は接続数と実行時間から見積もる
  6. 他社にサービスとして提供する形になっていないか、ライセンスの 3 つの制限と照らす

手元に残ったのは、起動手順と 3 つの落とし穴、トークンを取り込む経路のコマンド、dryrun で動いた処理 1 本、そして deploy が通らない理由です。クラウドの Free で定期同期を回した時点で、続報を書きます。

FDE を募集しています(会計FDE/FDE・業務システム)

経理の月次をAIで本番まで持っていく「会計FDE」(会計士・経理責任者・管理部門経験者)と、要件が固まる前に動くものを出して本番まで運ぶ「FDE(業務システム)」(ITコンサル・PM/PL・情シス出身者)を正社員で募集しています。応募でなくても構いません。まず30分の「キャリア相談」から。採用のお問い合わせフォームに「キャリア相談希望」と書いて送ってください。

freee・kintone・Slack との連携を、毎回ゼロから作るのをやめませんか

連携基盤を入れるか、ワークフロー型のツールで済ませるか、自前で持つかは、つなぐ相手の数と、トークンを誰が持つかで決まります。経理AX・kintone・案件管理システムの刷新で本番稼働まで伴走した経験をもとに、いまの連携の一覧を書き出すところから始められます。

30分・無料で相談する