リバースプロキシを作ってる話
この記事は「バーチャルケモミミ Advent Calendar 2025」の2日目です。
前回の記事は、 りらさんの にりみす技術基盤部 です。
まえがき
最近Cloudflare落ちましたよね。
参照2025年11月18日のCloudflareの障害
僕もほとんどのサービスをCloudflareでトンネリング、プロキシをしているので、すべてのサイトにアクセスできなくなりました。
これは困るということで、何とかCloudflareの利点を自分で作れないものかということで、検討して最近作っている「隠れ道(kakuremichi)」というプロジェクトを紹介しようと思います。
※執筆時点では認証、アクセス制限等の仕組みは存在しないので、プロダクション環境では利用できません。ご注意ください。
やりたいこと
Cloudflareのプロキシ、トンネリングで僕がいいなと思っている点はいくつかあります。
-
オリジンIPの隠蔽
個人サーバを運用している方には理解されると思いますが、自宅のIPアドレスが漏洩すると、DDoS攻撃の標的になったり、何かとリスクがあります。 -
SSL/TLS自動化
Let's Encryptの設定とか、証明書の更新とか、何も考えなくていい。 -
ポート開放不要
ルーターの設定をいじらなくても外部公開できる。
しかし、CloudflareにはBodySizeの制限があり、100MBまでしか上がらないというでかめのデメリットがあります。
これはMisskeyなど、でかめのペイロードを分割アップロードする機能がまだない(issueはある)サービスにとって、少し致命的です。
あと、今回の障害で思い知り ましたが、Cloudflareに依存しすぎると全部死ぬ。
なので、まあまあこういうのを自前で建てれたらいいなっておもいました。
りらさんと、めうるみさんを誘って3人で作り始めました
kakuremichi(隠れ道)とは
セルフホスト型のトンネルベースリバースプロキシシステムです。CloudFlare TunnelとPangolinにインスパイアされています。
ファイアウォールやNATの背後にあるサービスへ、隠れた道を通って安全にアクセスする、というイメージです。
アーキテクチャ
全体像

kakuremichiの全体アーキテクチャ
3つのコンポーネント
┌─────────┐
│ Control │ ← Web管理画面・REST API・設定配信
└─────────┘
↓ WebSocket ↓ WebSocket
(設定配信) (設定配信)
↓ ↓
[Gateway] [Agent]
↓ WireGuard ↓
外部 プライベート
ユーザー アプリ
| コンポーネント | 役割 | 技術スタック |
|---|---|---|
| Control | 中央管理サーバー | Node.js + TypeScript + Next.js |
| Gateway | 入口ノード、外部トラフィック受信 | Go + WireGuard |
| Agent | エッジクライアント、プライベートネットワーク側 | Go + WireGuard + netstack |
コントロールプレーンとデータプレーンの分離
■ コントロールプレーン(管理・設定)
Control ──WebSocket──> Gateway(設定を配信)
Control ──WebSocket──> Agent(設定を配信)
■ データプレーン(実際のトラフィック)
外部ユーザー → Gateway ─WireGuard─> Agent → アプリ
↑
Controlを経由しない!
この設計のメリット:
- 中央管理サーバーがボトルネックにならない
- Controlがダウンしても既存の通信は継続可能
- Gatewayを独立してスケール可能(複数配置でDDoS対策・負荷分散)
今回のCloudflare障害みたいな事態でも、データプレーンが生きていれば通信は継続できます。
マルチGateway対応
example.com のAレコード:
- 1.2.3.4 (Gateway1)
- 5.6.7.8 (Gateway2)
- 9.10.11.12 (Gateway3)
外部ユーザー
↓ DNS round robin → どれかのGateway
↓ HTTPS
Gateway(SSL終端)
↓ WireGuardトンネル
Agent
↓ ローカルプロキシ
プライベートアプリ
どのGatewayに接続しても、すべてのGatewayが同じAgentに到達できるため、透過的に動作します。
WireGuardのサブネット設計
トンネル毎に独立したサブネットを割り当てる設計です。トンネルを掘るたびに、AgentとGatwwayにIPが付与されて、サブネットが切れます。
こうすると各トンネルは自分のサブネット内でのみ通信可能で、Tunnel間の直接通信は不可能になります。WireGuardのAllowedIPsで厳密に制限できるのがいいですね。最大254サブネットまでスケールできるので、個人〜中規模なら十分です。
まあ、Gatwwayの台数が増えてきたら、サブネットのマスクの大きさを動的に確保できるような設計にすればいいかなと思っています。
技術的に面白かったところ
ゼロトラスト鍵管理
最初はControlサーバーで鍵ペアを一元管理しようと思ってたんですよ。でもよく考えたら、Controlが侵害されたら全秘密鍵が漏洩するんですよね。それはまずい。
なので、Agent/Gatewayが起動時に自分で鍵ペアを生成して、秘密鍵はメモリ内のみで保持する方式にしました。Controlには公開鍵だけ送る。Controlサーバーは秘密鍵を見ない・保存しない・送信しない。仮にControlが侵害されても、WireGuardの暗号化は破られません。
ユーザースペースWireGuard
Agentはプライベートネットワーク側で動作するので、できるだけ権限を必要としない設計にしたかったんです。
普通、WireGuardはカーネルモジュールとして動作するのでroot権限が必要なんですが、wireguard-goとgVisor netstackを組み合わせると完全にユーザースペースで動くWireGuardが作れます。
これでポート開放不要、root権限不要、Dockerコンテナ内でも動作。めっちゃ便利。
Let's Encrypt自動化
GatewayにACME HTTP-01チャレンジを実装しています。Goのautocertパッケージを使えば数行で書けます。
m := &autocert.Manager{
Cache: autocert.DirCache("certs"),
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(domains...),
}
証明書の取得・更新は完全自動。certbotとか何も考えなくていい。これがやりたかった。
管理画面の紹介
Control ServerにはWeb UIが付属しています。ここからAgent、Gateway、Tunnelの管理ができます。
ダッシュボード

システム全体の状態を一覧表示
接続中のAgent/Gatewayの数、アクティブなトンネル数、直近のイベントなどが確認できます。
Agent管理

登録されているAgentの一覧
各Agentの接続状態(オンライン/オフライン)、割り当てられたサブネット、最終接続時刻などが確認できます。
Gateway管理

登録されているGatewayの一覧
各Gatewayのエンドポイント、SSL証明書の状態、接続中のAgent数などが確認できます。
Tunnel設定

ドメイン名とターゲット(転送先)を指定するだけ。SSL証明書は自動取得されます。
進捗と今後
Control、Gateway、Agentの基本機能はほぼ完成しています。
今後、テストの環境としてでも運用にできるように、ユーザ認証、組織の仕組みを追加していく予定です。
これをすることで、Gatewayを組織を超えて共有したりすれば、Gatewayノードの分散化も可能になるかなと思っています。
マルチGateway対応にも似たようなことが書いてありますが、にりらとれぞらぶそれぞれGatewayをもって、それぞれ1台Gatewayを持つと、万が一どちらかのGatewayが落ちても、僕のルートで通信できるというメリットがあります。
(もちろん、信頼できる組織に限りますが)
使い方
# docker-compose.yml
services:
myapp:
image: my-app:latest
ports:
- "8080:8080"
agent:
image: kakuremichi/agent:latest
environment:
- CONTROL_URL=https://control.example.com
- API_KEY=agt_xxxxx
docker-compose upしたら、あとはControlのWeb UIでトンネルを設定するだけ。
おわりに
CloudFlare Tunnelを自作する旅、なんか既存のOSSがあるかもしれませんが、いろいろ創っていくの楽しいので、このプロジェクトはリリースまで進む気がします。
コントリビュートお待ちしてます
がんばる
明日は †真けわーい† さんの記事です。お楽しみに!
Discussion