別記事(前編 / 後編)で、Dify Community 版の手前に nginx + oauth2-proxy + Keycloak を置き、EntraID SSO と部署単位の認可を実装しました。この記事では、その前段(front-nginx)を インターネットに公開する方法 をまとめます。
やりたいのは次の2点です。
- ルーターのポート開放や固定 IP なしで公開したい
- 自己署名ではなく、正規の TLS 証明書 でアクセスさせたい
この2つを Cloudflare Tunnel で解決します。
TL;DR
- Cloudflare Tunnel(
cloudflared)は、ローカルから Cloudflare へ アウトバウンド接続のみ を張って公開します。ポート開放・固定 IP は不要 です - TLS は Cloudflare エッジが正規証明書で終端 します。オリジン(front-nginx)は自己署名で構いません
-
一番の注意点: 無料の Universal SSL は 「apex + 1階層のワイルドカード」 までしかカバーしません。
auth.dify.example.comのような 2階層のサブドメインは Advanced Certificate Manager(ACM、有料) が必要です -
cloudflaredは front-nginx へhttps://localhost:443に No TLS Verify で接続します(オリジンの自己署名を検証しない) - 設定はすべて Cloudflare の ダッシュボード(Public Hostname 画面) から行います。この画面はホスト名の追加と同時に DNS レコードも自動作成します
- 注意: DNS レコードを先に個別で作成していると、Public Hostname 画面での追加が「既に存在する」というエラーで失敗します
前提
- Cloudflare に登録済みのドメイン(無料プランで可)。この記事では
example.comとします - front-nginx が ホストの 443 番 で TLS 終端している(別記事の構成)
-
cloudflaredがホストで起動している(トークン方式=ダッシュボード管理のトンネルを想定)
構成
ブラウザからのリクエストは、Cloudflare のエッジで TLS 終端され、トンネル経由でローカルの front-nginx に届きます。
Cloudflare エッジ〜ブラウザ間は正規証明書、cloudflared〜front-nginx 間はローカル通信なので自己署名のままで問題ありません。
1. Cloudflare Tunnel とは、なぜ使うのか
cloudflared はホスト上で常駐し、Cloudflare に対して 外向きの接続 を確立します。公開ホスト名(例 app.example.com)へのリクエストは、この接続を通ってローカルのサービスへ届きます。
- インバウンドのポート開放が不要:接続は常にローカル発なので、ルーターやファイアウォールに穴を開ける必要がありません
- 固定 IP が不要:DNS は Cloudflare 側で完結します
- 正規 TLS がエッジで付く:証明書の取得・更新を Cloudflare が担います
特に便利なのが、実際のドメインと正規の TLS 証明書が必要な検証の場面です。例えば EntraID などの外部 IdP と連携する認証・認可を検証する場合、多くの IdP はリダイレクト URI に実在する https の URL を要求します。localhost や自己署名証明書では、IdP 側の設定やブラウザの挙動が本番と食い違い、そこが原因なのか実装が原因なのか切り分けにくくなります。Cloudflare Tunnel を使えば、ローカルで動かしている環境をそのまま実ドメイン・正規証明書でインターネットに公開できるため、本番相当の URL で認証フローを検証しながら、コードはローカルで動かして素早く直す、という開発サイクルを回せます。
2. ドメイン設計と証明書の落とし穴(重要)
ここが最も注意すべき点です。無料の Universal SSL がカバーする範囲 は次のとおりです。
| ホスト名の例 | 無料 Universal SSL | 備考 |
|---|---|---|
example.com |
✅ | apex |
app.example.com |
✅ | 1階層のサブドメイン |
auth.example.com |
✅ | 1階層のサブドメイン |
app.dify.example.com |
❌ | 2階層。ACM(有料)が必要 |
auth.dify.example.com |
❌ | 2階層。ACM(有料)が必要 |
つまり、サブドメインを1階層に収めれば無料 で済みます。
- 無料で済ませたい:
app.example.com(Dify)、auth.example.com(Keycloak)のように 1階層 に揃える - どうしても
dify配下にまとめたい(app.dify.example.com等): Advanced Certificate Manager(ACM、月額課金) で*.dify.example.comの証明書を発行する
この記事では、無料で済む 1階層の構成 (app.example.com / auth.example.com)で進めます。
3. Public Hostname を追加する
Zero Trust > Networks > Tunnels > 対象トンネル > Configure > Public Hostname > Add から、公開したいホスト名を次のとおり追加します。
| Subdomain | Domain | Type | URL | 追加設定 |
|---|---|---|---|---|
app |
example.com |
HTTPS | localhost:443 |
TLS → No TLS Verify = ON |
auth |
example.com |
HTTPS | localhost:443 |
TLS → No TLS Verify = ON |
この操作だけで、公開ホスト名(トンネル宛ての Proxied な CNAME)と、トンネルの転送設定の両方が作られます。DNS 側を別途操作する必要はありません。
注意: 同名の DNS レコードを先に個別で作成していると、この画面での追加が「既に存在する」というエラーで失敗します。すでに作成してしまっている場合は、DNS 画面でそのレコードを削除してからやり直してください。
ポイントを補足します。
-
なぜ HTTPS + No TLS Verify なのか:front-nginx は自己署名証明書で 443 を TLS 終端しています。
cloudflaredをhttps://localhost:443に向け、No TLS Verify でオリジン証明書の検証を無効化することで、front-nginx の設定を一切変えずに接続できます。ブラウザが見るのは Cloudflare の正規証明書なので、オリジンの自己署名は問題になりません。 -
ルーティングは Host ヘッダで行われる:
cloudflaredは元の Host ヘッダ(app.example.comなど)をそのまま渡すため、front-nginx はその Host でserver_nameを選び、正しいserver {}に振り分けます。 - 既存トンネルに追加する場合:すでに別サービスを公開しているトンネルであっても、Public Hostname 画面は既存のホスト名設定を保持したまま新しいものを追加できます。以前の公開設定が消える心配はありません。
4. (2階層構成の場合のみ)ACM を発注する
app.dify.example.com のような2階層構成にした場合は、無料 Universal SSL では証明書エラーになります。ダッシュボード > 対象ドメイン > SSL/TLS > Edge Certificates > Order Advanced Certificate から、*.dify.example.com を含む証明書を発注します(有効化まで数分〜十数分)。1階層構成なら不要です。
5. 動作確認
トンネル経由でアクセスし、front-nginx まで届いているかを確認します。SSO が有効なら、未ログイン時は Keycloak へリダイレクト(302)されます。
$ curl -sS -o /dev/null -w "%{http_code} -> %{redirect_url}\n" https://app.example.com/
302 -> https://auth.example.com/realms/tenant1/protocol/openid-connect/auth?...
ブラウザで https://app.example.com/ を開き、証明書の警告が出ない(=エッジの正規証明書)こと、Keycloak のログインに遷移することを確認します。
6. ハマりどころ
2階層サブドメインで SSL handshake failure
auth.dify.example.com のような2階層構成で、ブラウザや curl が sslv3 alert handshake failure を返す場合、無料 Universal SSL のカバー外 です。ACM を発注し、証明書が Active になるまで待ちます。1階層構成にすればそもそも発生しません。
ホストの 443 に届かない / ポートの競合
cloudflared は https://localhost:443 に接続するため、front-nginx がホストの 443 を確実にリッスンしている必要があります。なお、Cloudflare Tunnel 経由では 443 だけあれば十分 で、HTTP(80) をホストに公開する必要はありません。ホストの 80 が別プロセスと競合していても、443 さえ空いていれば問題ありません。
オリジンが http だと X-Forwarded-Proto がずれる
cloudflared を http://localhost:80 に向けると、front-nginx から見た $scheme が http になり、Keycloak 等の「https を期待している」処理とずれてループしがちです。front-nginx 自身に TLS 終端させ、cloudflared は https://localhost:443(No TLS Verify)に向ける ことで、この問題を避けられます。
まとめ
Cloudflare Tunnel を使うことで、Dify の前段(nginx + oauth2-proxy + Keycloak)を、
- ポート開放・固定 IP なしで
- エッジの正規 TLS 証明書付きで
インターネットに公開できました。証明書まわりは、サブドメインを1階層に収めれば無料、2階層以上にするなら ACM(有料)という点だけ押さえておけば、運用で困りません。
認証・認可の実装は前編 / 後編の記事にまとめています。あわせて読むと、EntraID SSO 付きの Dify を公開するまでの全体像がつかめます。