Cloudflare Tunnelのingressで複数サブドメインを振り分ける設定例

※本記事にはプロモーション(広告・アフィリエイトリンク)を含みます。
自宅のLinux機で複数のNodeアプリをsystemd常駐させ、ポート開放せずにCloudflareTunnelで公開しています。このとき悩むのが「トンネルを1本にまとめて、shop.example.comはポート3000、api.example.comは別ポート、へと振り分けたい」というケースです。この記事は、1本のトンネルで複数サブドメインをingressルールで振り分ける設定例と、実際に動かすまでの手順・検証方法・つまずきやすい点を順番にまとめたものです。
トンネル1本で複数サブドメインを捌く仕組み
Cloudflare Tunnelは、サーバー側で動くcloudflaredがCloudflareのエッジへアウトバウンド接続を張り、外から来たリクエストをその接続経由でローカルへ流し込む仕組みです。インバウンドのポート開放は不要で、私もルーターのポートは一切開けていません。
トンネルを何本も立てる必要はありません。1本のトンネルに複数のingressルール(公開ホスト名とローカルサービスの対応表)を並べ、受け取ったリクエストのホスト名を上から順に照合して、最初に一致したサービスへ振り分けます。この「上から順・最初に一致したもの勝ち」という評価順が設定の肝です。
設定ファイル(config.yml)の書き方
ローカル管理のトンネルでは、~/.cloudflared/config.yml(配置場所は環境により異なります)に以下のように書きます。
tunnel: <TUNNEL-ID>
credentials-file: /home/USER/.cloudflared/<TUNNEL-ID>.json
ingress:
# 1. ECサイト本体
- hostname: shop.example.com
service: http://localhost:3000
# 2. API窓口(別プロセス・別ポート)
- hostname: api.example.com
service: http://localhost:8793
# 3. パスで分けたい場合(正規表現で指定)
- hostname: shop.example.com
path: ^/admin/.*
service: http://localhost:3100
# 4. どれにも一致しなかったときの受け皿(必須)
- service: http_status:404
ポイントは3つです。
hostnameを持つルールは具体的なものほど上に置く。上から照合され最初の一致で確定するため、順番を間違えると意図しないサービスへ流れます。pathは正規表現で、同じホスト名でもパスごとに別サービスへ分けられます(pathを省けばそのホスト名の全パスが対象)。- 最後のルールは必ず
hostnameなしのキャッチオールにする。これが無いと設定検証で弾かれます。到達不能にしたいならhttp_status:404が便利です。
ワイルドカードで一括りにしたいときはhostname: "*.example.com"と書けますが、個別指定より下に置かないと先に拾われてしまいます。
HTTPSのオリジンで証明書を自前にしている等の場合は、ルールごとにoriginRequestで挙動を調整できます。
- hostname: internal.example.com
service: https://localhost:8443
originRequest:
noTLSVerify: true
httpHostHeader: internal.example.com
DNSを紐づけてトンネルを起動する
ingressを書いただけでは外から届きません。各ホスト名をトンネルへ向けるCNAMEレコードが必要です。cloudflaredから登録できます。
cloudflared tunnel route dns <TUNNEL-NAME> shop.example.com
cloudflared tunnel route dns <TUNNEL-NAME> api.example.com
これでCloudflare DNSに<TUNNEL-ID>.cfargotunnel.comを指すCNAME(プロキシ有効)が作られます。既に同名レコードがある場合は手動で整理してから実行してください。
起動は手動ならcloudflared tunnel run <TUNNEL-NAME>、常駐させるならサービス化します。
sudo cloudflared service install
sudo systemctl enable --now cloudflared
sudo systemctl restart cloudflared # 設定変更を反映
設定を書き換えたら再起動を忘れないこと。config.ymlを直したのに反映されない場合、再起動していないだけ、ということがよくあります。
書いたら必ず検証してから流す
cloudflaredにはingressの静的検証と、ルーティングのドライランができるサブコマンドがあります。公開前にこれで確認します。
# 構文とキャッチオールの有無をチェック
cloudflared tunnel ingress validate
# このURLがどのサービスに振り分けられるか確認
cloudflared tunnel ingress rule https://api.example.com/health
ruleは実際にリクエストを送らず、どのルールにマッチするかだけを表示してくれるので、順番ミスの切り分けに重宝します。
つまずきやすい点
引っかかりやすいと感じる点をまとめます。
| 症状 | 原因として考えられること |
|---|---|
| config.ymlを直しても変わらない | ダッシュボードのRemote管理トンネルでは、ローカルのconfig.ymlは読まれません。Web側の公開ホスト名設定を使います |
| 特定サブドメインだけ404/別サービスに届く | ルールの順番。具体的なhostnameをワイルドカードやキャッチオールより上へ |
| 502になる | ローカルサービスが落ちている、ポート番号違い、http/httpsの指定違い。serviceのURLを実際にローカルからcurlして確認 |
| validateで弾かれる | 末尾のキャッチオールルールが無い |
Remote管理(ダッシュボードで作成・設定)とローカル管理(config.yml)の混在が、原因の分かりにくいトラブルを生みやすい印象です。どちらで管理するかを最初に決めておくのをおすすめします。なお料金や挙動の細部は変わりうるので、最終的な仕様は公式ドキュメントで確認してください(執筆時点の情報です)。
まとめ
複数サブドメインの振り分けは、トンネルを増やすのではなく、1本のトンネルのingressルールを「具体的なものを上・キャッチオールを末尾」の順で並べるのが基本形です。次の一歩として、いまのconfig.ymlにcloudflared tunnel ingress validateをかけ、キャッチオールの有無とルールの順番だけでもチェックしてみてください。ここだけで多くの振り分け事故は防げます。
よくある質問
複数サブドメインごとにトンネルを分けるべき?
通常は1本で十分です。1つのトンネルにingressルールを複数並べ、ホスト名ごとに別ポートのローカルサービスへ振り分けられます。管理対象が増えるため、分けるのは隔離要件がある場合に限るのが扱いやすいです。
config.ymlを編集しても設定が反映されません。
まず`cloudflared`の再起動を確認してください。それでも変わらない場合、ダッシュボードで作成したRemote管理トンネルの可能性があります。その構成ではローカルのconfig.ymlは読まれず、Web側の公開ホスト名設定が使われます。
ingressの最後に書くhttp_status:404は何のため?
どのhostnameルールにも一致しなかったリクエストの受け皿です。cloudflaredはhostnameなしのキャッチオールを末尾に必須とし、無いと検証で弾かれます。到達不能にしたいホストを404で弾く用途に使えます。
同じサブドメインでパスごとに別サービスへ分けられますか?
できます。ingressルールにpathを正規表現で指定すると、同一hostnameでもパスごとに異なるローカルサービスへ振り分けられます。pathを省略するとそのhostnameの全パスが対象になります。