Cloudflare Tunnelで複数のWebサービスをサブドメイン公開する方法

1. はじめに

自宅PCで複数のWebアプリを動かしていると、サービスごとに分かりやすいURLを割り当てたくなります。

たとえば、次のような分け方です。

example.com         → メインサイト
asset.example.com   → アセット管理
api.example.com     → API
panel.example.com   → LAN内の別PCにある管理画面

これをルーターのポート開放とリバースプロキシだけで構築する方法もありますが、今回はCloudflare Tunnelを使用します。

Cloudflare Tunnelでは、cloudflaredが自宅側からCloudflareへ接続します。そのため、Webサービスを公開するためにルーターへ外部からの受信ポートを開けなくても構成できます。

この記事では、Windows上でローカル管理型のトンネルを動かし、1本のトンネルへ複数のホスト名を登録する例を扱います。LinuxやmacOSでは、設定ファイルのパスやサービス登録方法が異なります。

なお、実際に使用しているトンネルID、Windowsユーザー名、認証ファイルのパス、ドメインはすべて匿名化しています。

2. Cloudflare Tunnelとは

Cloudflare Tunnelは、ローカル環境で動くcloudflaredとCloudflareのネットワークを接続する仕組みです。

通常のポート開放では、インターネット側から自宅ルーターへ届いた通信を、LAN内のPCへ転送します。

通常のポート開放

利用者
  ↓ 外部から自宅へ接続
ルーターの公開ポート
  ↓
自宅サーバー

Cloudflare Tunnelでは、cloudflaredが先にCloudflareへ外向きの接続を作ります。

Cloudflare Tunnel

自宅のcloudflared
  ↓ 外向きに接続
Cloudflare
  ↑
利用者

利用者からのHTTPSリクエストはCloudflareが受け取り、すでに作られているトンネルを通してcloudflaredへ渡します。自宅側でインターネットからの新規接続を直接待ち受ける必要がないため、ルーターの受信ポートを開けずにWebサービスを公開できます。

ただし、「何も通信許可が要らない」という意味ではありません。cloudflaredからCloudflareへの外向き通信が、ローカルや組織のファイアウォールで許可されている必要があります。

また、Cloudflare Tunnelを使用しても、公開したWebアプリ自体の脆弱性や認証不足が解消されるわけではありません。

3. 今回作る構成

今回の通信経路は、次のようになります。

利用者
  ↓ HTTPS
Cloudflare
  ↓ 暗号化されたトンネル
cloudflared
  ├─ localhost:1223
  ├─ localhost:3000
  ├─ localhost:3011
  └─ 192.168.1.4:8080

cloudflaredと同じPCで動くサービスにはlocalhostを指定します。LAN内の別PCで動くサービスには、そのPCのLAN内IPアドレスを指定します。

1本のトンネルでも、複数のサブドメインを扱えます。

example.com
  ↓
localhost:1223

asset.example.com
  ↓
localhost:3011

api.example.com
  ↓
localhost:3000

panel.example.com
  ↓
192.168.1.4:8080

Cloudflareまでの入口はホスト名で分かれていますが、自宅側ではそれぞれ別のポートや別PCへ振り分けられます。

この記事の対象範囲

この記事では、HTTPまたはHTTPSで動くWebサイト、API、管理画面などの公開を中心に説明します。

Minecraft Java版の通常接続のような、HTTPではない任意のTCPサービスは、Cloudflareのプラン、対応製品、クライアント側の接続方法などが異なります。Webサイトの公開と同じ設定を追加するだけで、一般のMinecraftクライアントから接続できるとは限りません。

非HTTPサービスを公開したい場合は、対象プロトコルに対応するCloudflareの最新公式ドキュメントと利用プランを確認してください。

4. 必要なもの

構築前に、次のものを用意します。

  • Cloudflareで管理しているドメイン
  • Cloudflareアカウント
  • cloudflared
  • 公開したいローカルWebサービス
  • Cloudflareへ接続できるネットワーク
  • 設定ファイルを編集できる権限

Windowsサービスの登録や再起動では、管理者権限が必要になる場合があります。

最初にcloudflaredが利用できるか確認します。

cloudflared version

コマンドが見つからない場合は、cloudflared.exeがあるフォルダで実行するか、そのフォルダへPATHを通します。

Windows版のcloudflaredは、導入方法によっては自動更新されません。古いバージョンを長期間使っている場合は、更新方法も公式ドキュメントで確認します。

この記事はWindowsを中心に説明します。Linuxではsystemd、macOSではlaunchdなどを使う場合があり、設定ファイルの探索場所もWindowsとは異なります。

5. トンネルを作成する

ここでは、CLIで管理する名前付きトンネルを例にします。

Cloudflareへログインする

cloudflared tunnel login

このコマンドを実行すると、通常はブラウザが開きます。Cloudflareへログインし、トンネルで使用するドメインを選択します。

認証が完了すると、ローカル管理型トンネルの作成に使用する認証ファイルが生成されます。このファイルも第三者へ公開しないようにします。

名前付きトンネルを作る

cloudflared tunnel create my-tunnel

my-tunnelは、管理しやすいトンネル名の例です。このコマンドによってトンネルが登録され、トンネルIDと実行用のcredentials JSONが生成されます。

作成結果は次のコマンドで確認できます。

cloudflared tunnel list

トンネルを作っただけでは、まだ各サブドメインからローカルサービスへアクセスできません。続いてDNSルートとconfig.ymlを設定します。

コマンド名、認証画面、生成先はcloudflaredのバージョンによって変わる可能性があります。実際の出力とCloudflare公式ドキュメントを確認しながら進めてください。

6. DNSルートを登録する

公開したいホスト名を、作成したトンネルへ向けます。

cloudflared tunnel route dns my-tunnel app.example.com

このコマンドは、指定したホスト名をトンネルへ向けるDNSレコードをCloudflare側に作ります。ローカル管理型トンネルの現在の公式説明では、トンネル用のcfargotunnel.comホスト名を指すCNAMEが作成されます。

複数のホスト名を使う場合は、それぞれにDNSルートが必要です。

cloudflared tunnel route dns my-tunnel example.com
cloudflared tunnel route dns my-tunnel asset.example.com
cloudflared tunnel route dns my-tunnel api.example.com
cloudflared tunnel route dns my-tunnel panel.example.com

DNSルートとconfig.ymlの関係

DNSルートとconfig.ymlには別々の役割があります。

DNSルート
  このhostnameを、どのトンネルへ送るか

config.ymlのingress
  トンネルへ届いたhostnameを、どのローカルserviceへ送るか

たとえば、api.example.comのDNSルートがmy-tunnelを指し、config.ymlapi.example.comhttp://localhost:3000を指して初めて、目的のAPIへ届きます。

api.example.com
  ↓ DNS
my-tunnel
  ↓ ingress
http://localhost:3000

DNSルートだけを作っても、対応するingressルールがなければ目的のサービスへ振り分けられません。反対に、config.ymlへhostnameを書いても、DNSルートがなければ利用者の名前解決がトンネルへ向きません。

同じ名前のA、AAAA、CNAMEレコードがすでにある場合は、DNSルートの作成に失敗することがあります。既存レコードの用途を確認してから整理します。

7. config.ymlに複数サービスを書く

Windowsのユーザーとして手動実行する場合、既定の設定フォルダは一般に次の場所です。

C:\Users\YOUR_NAME\.cloudflared\config.yml

ただし、--configで別のファイルを指定している場合や、Windowsサービスとして動かしている場合は別の場所を使用していることがあります。

匿名化した設定例は次のとおりです。

tunnel: YOUR_TUNNEL_ID
credentials-file: C:\Users\YOUR_NAME\.cloudflared\YOUR_TUNNEL_ID.json

ingress:
  - hostname: example.com
    service: http://localhost:1223

  - hostname: asset.example.com
    service: http://localhost:3011

  - hostname: api.example.com
    service: http://localhost:3000

  - hostname: panel.example.com
    service: http://192.168.1.4:8080

  - service: http_status:404

tunnel

tunnel: YOUR_TUNNEL_ID

どの名前付きトンネルを動かすか指定します。実際には、トンネル作成時に発行されたIDを使用します。

公開記事や質問へ設定を貼る場合は、今回のように匿名化しておくと安全です。

credentials-file

credentials-file: C:\Users\YOUR_NAME\.cloudflared\YOUR_TUNNEL_ID.json

トンネルを実行するためのcredentials JSONを指定します。

このファイルは認証に使われるため、パスだけでなくJSON本体を特に厳重に扱います。GitHub、ブログ、スクリーンショット、共有フォルダなどへ公開しないでください。

Windowsサービスとして実行する場合、サービス用アカウントがこのファイルを読める場所と権限になっている必要があります。

ingress

ingress:

トンネルへ届いた通信を、どのサービスへ送るか定義するルール一覧です。

ingressルールは上から順番に評価され、最初に一致したルールが使われます。範囲の広いルールを上へ置くと、その下のルールへ到達しない場合があるため、具体的なhostnameを先に書きます。

hostname

- hostname: api.example.com

利用者がブラウザでアクセスする公開ホスト名です。Cloudflare側のDNSルートにも同じホスト名を登録します。

service

service: http://localhost:3000

そのhostnameに届いたリクエストを、cloudflaredがどこへ転送するか指定します。

http://https://は、ローカルサービスが実際に提供している方式に合わせます。ローカルサービスがHTTPなのにhttps://を指定すると、接続やTLSのエラーになる可能性があります。

最後のhttp_status:404

- service: http_status:404

これは、上のどのルールにも一致しなかったリクエストへHTTP 404を返すcatch-allルールです。

Cloudflareのingress設定では、最後にすべてのリクエストへ一致するルールが必要です。hostnameを書かないこのルールが、残った通信を受け止めます。

一致するhostnameあり
  → 対応するローカルサービスへ転送

一致するhostnameなし
  → 最後のhttp_status:404

このルールがあることで、想定していないホスト名を誤って別サービスへ転送せず、明示的に404で終了できます。

8. localhostとLAN内IPの違い

localhostは、cloudflaredが動いているPC自身を指します。

service: http://localhost:3000

この設定は、cloudflaredと同じPCのポート3000へ接続します。

別PCで動いているサービスへ接続する場合は、そのPCのLAN内IPアドレスを使用します。

service: http://192.168.1.4:8080

通信経路は次のようになります。

cloudflaredが動くPC
  ↓ LAN
192.168.1.4:8080の別PC

別PCを指定するときは、次の点を確認します。

  • cloudflaredのPCからhttp://192.168.1.4:8080へ直接アクセスできるか
  • 対象サービスが127.0.0.1だけでなくLAN側でも待ち受けているか
  • 対象PCのファイアウォールがLANからの接続を許可しているか
  • PC間を分離するWi-Fi設定やVLANで遮断されていないか

LAN内IPアドレスがDHCPによって変わると、config.ymlが古いIPを指して接続できなくなります。ルーターのDHCP予約や、環境に合った固定IP設定を検討します。

cloudflaredから別PCまでをHTTPで接続する場合、その区間はLAN内のHTTP通信です。LAN内でも暗号化が必要な環境では、ローカルサービス側のHTTPS化も検討してください。

9. トンネルを起動する

手動で起動する場合

既定のconfig.ymlを使う場合は、次のように起動します。

cloudflared tunnel run my-tunnel

設定ファイルを別の場所へ置いている場合は、パスを明示します。

cloudflared tunnel --config C:\path\to\config.yml run my-tunnel

起動中のコンソールには、Cloudflareへの接続状態やエラーが表示されます。最初の動作確認では手動実行にしておくと、ログをその場で確認しやすくなります。

手動実行は、コンソールを閉じたりPCを再起動したりすると停止します。

Windowsサービスとして常時起動する場合

常時公開するなら、Windowsサービスとして登録する方法があります。

現在の公式手順では、管理者権限のコマンドプロンプトなどから次のコマンドを使用する例があります。

cloudflared service install

ただし、ローカル管理型とダッシュボード管理型では、サービスの登録手順や必要な引数が異なる場合があります。使用しているトンネル方式とcloudflaredのバージョンに対応した公式手順を確認してください。

Windowsサービスは、手動実行時のユーザーとは異なるサービスアカウントで動く場合があります。その場合、通常のユーザープロファイルにあるconfig.ymlやcredentials JSONを自動では読めないことがあります。

サービスへ登録された実際の実行コマンドと設定パスは、次のようなコマンドで確認できます。

Get-CimInstance Win32_Service |
  Where-Object { $_.Name -like "*cloudflared*" } |
  Select-Object Name, State, PathName

PathName--configがあれば、その後ろのファイルが実際に読み込まれる設定候補です。

Linuxではsystemd、macOSではlaunchdを使用する構成があります。インストール先と設定パスがWindowsとは異なるため、それぞれの公式サービス登録手順を確認します。

10. 設定変更を反映する

config.ymlを保存しただけでは、すでに動いているcloudflaredへ変更が反映されない場合があります。まず設定を検証し、その後に実行中のトンネルを再起動します。

ingressルールを検証する

cloudflared tunnel ingress validate

YAMLの書式やcatch-allルールなど、ingress設定が有効か確認します。

特定のURLがどのルールへ一致するかは、次の例で確認できます。

cloudflared tunnel ingress rule https://api.example.com

意図したapi.example.comのルールへ一致するか確認します。

これらのコマンドを利用できないバージョンでは、cloudflared tunnel helpと最新の公式ドキュメントを確認してください。また、既定以外の設定ファイルを使用している場合は、検証コマンドがどのファイルを読んだか出力を確認します。

手動実行を再起動する

手動起動しているコンソールをCtrl+Cで停止し、同じ設定ファイルを指定して起動し直します。

cloudflared tunnel run my-tunnel

Windowsサービスを再起動する

サービス名がcloudflaredの場合の例です。

Restart-Service cloudflared

サービスの操作には管理者権限が必要になる場合があります。また、サービス名は導入方法によって異なる可能性があります。

Get-Service *cloudflared*

表示された実際のサービス名を使用してください。

設定変更が反映されない主な原因

  • 編集したconfig.ymlと、実際に読み込まれているファイルが違う
  • 古いcloudflaredプロセスが動いたまま
  • Windowsサービスとして別の設定で動作している
  • 手動起動とサービス起動が重複している
  • YAMLのインデントが間違っている
  • トンネルを再起動していない
  • 新しいhostnameのDNSルートを作成していない
  • ローカル側のWebサービスが起動していない

実行中のプロセスは次の例で確認できます。

Get-Process cloudflared -ErrorAction SilentlyContinue

手動実行とサービス実行が同時に存在すると、どちらのログや設定が使われているのか分かりにくくなります。まず実行方法を一つに整理して確認します。

11. 動作確認

外部URLだけを見るのではなく、内側から順番に確認すると原因を切り分けやすくなります。

1. ローカルURLへ直接アクセスする

cloudflaredが動くPCで、転送先へ直接アクセスします。

curl.exe -I http://localhost:1223
curl.exe -I http://localhost:3000
curl.exe -I http://localhost:3011
curl.exe -I http://192.168.1.4:8080

アプリによってはHEADリクエストへ対応していないため、-Iで期待した結果が出ないときはブラウザや通常のGETでも確認します。

ローカルURLへ直接つながらなければ、Cloudflare Tunnelより先にWebサービスの起動状態、待受アドレス、ポート、LAN内ファイアウォールを直します。

2. DNSの名前解決を確認する

Resolve-DnsName api.example.com

または、次のコマンドでも確認できます。

nslookup api.example.com

DNSルートを追加した直後は、キャッシュなどによって結果の反映に時間差が出る場合があります。

3. 公開URLへアクセスする

ブラウザで次のようなURLへアクセスします。

https://example.com
https://asset.example.com
https://api.example.com
https://panel.example.com

HTTPステータスだけを確認する例です。

curl.exe -I https://api.example.com

4. cloudflaredのログを確認する

手動実行ならコンソール、サービス実行ならサービスの設定で指定されたログやイベント情報を確認します。

Cloudflareへの接続が成立しているか
どのorigin serviceへ接続しようとしたか
connection refusedが出ていないか
TLSや証明書のエラーがないか
読み込んだconfig.ymlはどれか

ログの保存先や詳細度は、起動オプションやサービス設定によって異なります。

12. エラー別の確認方法

502 Bad Gateway

Cloudflare Tunnel経由で表示される502 Bad Gatewayは、トンネル自体はCloudflareへ接続しているものの、cloudflaredから設定されたローカルサービスへ到達できないときに出ることがあります。

主な確認項目は次のとおりです。

  • ローカルWebサービスが起動しているか
  • serviceのポートが正しいか
  • http://https://を間違えていないか
  • localhostが本当にcloudflaredと同じPCか
  • LAN内IPアドレスが変わっていないか
  • LAN内ファイアウォールが拒否していないか
  • HTTPS originの証明書検証で失敗していないか

最初に、config.ymlへ書いたservice URLへcloudflaredのPCから直接アクセスします。

404 Not Found

最後のhttp_status:404が返っている場合は、アクセスしたhostnameが上のどのingressルールにも一致していない可能性があります。

cloudflared tunnel ingress rule https://api.example.com

このコマンドで、どのルールへ一致するか確認します。

ただし、ローカルのWebアプリ自身が404を返している可能性もあります。cloudflaredのcatch-allによる404なのか、転送先アプリの404なのかをログとローカルアクセスで切り分けます。

Connection refused

cloudflaredのログにconnection refusedが出る場合は、指定したserviceのアドレスとポートで接続を受け付けるプロセスが見つからない可能性があります。

service: http://localhost:3000
                         ↑
                3000で待受しているか確認

サービスが停止している、実際のポートが違う、起動途中、異常終了、別PCなのにlocalhostを指定した、といった原因を調べます。

DNSで名前を解決できない

DNSルートが作成されているか、CloudflareのDNS管理画面で同名レコードが競合していないか確認します。

config.ymlへhostnameを追加しただけでは、DNSレコードは作成されません。

設定を変えたのに古い転送先へ届く

次の順番で確認します。

1. 実際に読まれているconfig.ymlのパス
2. ingress validateの結果
3. ingress ruleの一致結果
4. 手動プロセスとWindowsサービスの重複
5. cloudflaredを再起動したか
6. DNSが別トンネルを指していないか

特にWindowsサービスでは、サービスアカウント側の.cloudflaredフォルダや、サービス登録時に指定した--configを使用している場合があります。自分のユーザーフォルダだけを編集し続けても反映されません。

13. セキュリティ

Cloudflare Tunnelはルーターの受信ポートを開けずに公開できますが、それだけでWebサービス全体が安全になるわけではありません。

  • credentials JSONを公開しない
  • credentials JSONの読み取り権限を必要なユーザーだけにする
  • cert.pemなどの認証ファイルも公開しない
  • GitHubへ載せる設定例ではユーザー名、パス、ドメイン、IDを匿名化する
  • 管理画面にはCloudflare Accessなどによる認証を検討する
  • ローカルWebサービス自体のログインやアクセス制御も有効にする
  • アプリと依存ライブラリを更新する
  • 設定変更前にcredentialsとconfigの安全なバックアップを取る
  • ログやスクリーンショットにトークンが写っていないか確認する

トンネルIDはトンネルを識別する情報ですが、実行権限につながるcredentials JSONは特に重要です。認証ファイルの内容を質問サイトや生成AIへ貼り付けないでください。

管理画面をpanel.example.comとして公開する場合、URLを知っている人なら誰でもログイン画面へ到達できる状態にしないよう、Cloudflare Accessなどで利用者を制限する方法を検討します。

Cloudflare側の認証を追加しても、アプリ側の認証や権限確認を省略してよいとは限りません。複数の防御を組み合わせます。

14. まとめ

Cloudflare Tunnelでは、1台のPCで動く複数のWebサービスや、LAN内の別PCにあるWebサービスを、1本のトンネルでサブドメインごとに公開できます。

利用者
  ↓ HTTPS
Cloudflare
  ↓ Tunnel
cloudflared
  ├─ example.com       → localhost:1223
  ├─ asset.example.com → localhost:3011
  ├─ api.example.com   → localhost:3000
  └─ panel.example.com → 192.168.1.4:8080

重要なポイントは次のとおりです。

hostname
  利用者がアクセスする公開側の名前

service
  cloudflaredから接続するローカル側のURL

DNSルート
  hostnameをトンネルへ向ける

ingress
  トンネルに届いたhostnameをserviceへ振り分ける

最後のhttp_status:404
  どのルールにも一致しない通信を受け止める

設定変更が反映されないときは、編集したファイルではなく「実行中のcloudflaredが実際に読んでいる設定ファイル」を確認することが大切です。手動起動とWindowsサービスの重複、サービスアカウント側のパス、再起動忘れを順番に確認します。

また、localhostcloudflaredが動いているPC自身です。別PCのサービスを公開するなら、到達可能なLAN内IPアドレスを指定し、IPが変わらないようDHCP予約などを検討します。

最後に、この記事はHTTP・HTTPSのWeb公開を対象にしています。Minecraftなどの非HTTPサービスは、対応方式とプランを個別に確認してください。

参考にした公式情報