ngrok の代わりに Cloudflare Tunnel を使い、ローカルサーバーを独自ドメインで公開する

## TL;DR

- ngrok では、ローカルの複数サーバを複数ドメインで公開したい時に Cookie 周りで不自由があります
  - 代わりに Cloudflare Tunnel を利用することで問題を解消できました
- 設定が意外と簡単だったので、設定方法と実行方法をまとめました
  - 設定方法だけ知りたい場合は、[設定方法と実行方法](#設定方法と実行方法)を参照してください

## はじめに

Passkeys に関わる実装など、ローカルで開発時にドメインが欲しくなることも少なくありません。そういった場合、やり方は幾つかありますが、手軽に実現できる手段としてリバースプロキシしてくれる君こと [ngrok](https://ngrok.com/) が利用できます。


しかし、 ngrok は[フリープラン]((https://ngrok.com/docs/pricing-limits/free-plan-limits/#free-plan-resources))の場合、次のような問題があります。
- フロントとサーバ実装のドメインを分ける場合、 Cookie の `SameSite=Lax` が Chrome 等のブラウザで共用できない
  - [Public Suffix List(PSL)](https://github.com/publicsuffix/list) に登録されている `ngrok-free.app` を Cookie の Domain 属性として持つことになるため
- サブドメインが起動ごとに更新されるため
  - CORS のための `Access-Control-Allow-Origin` を都度更新するのが面倒
  - [フリープランでも固定ドメインが利用できるようになった](https://ngrok.com/blog-post/free-static-domains-ngrok-users)が、依然として1つ目の問題は解決できない

そこで、 [Google Domains の終了](https://domains.google/)に伴い、Cloudflare 移管した自分のドメインの存在を思い出しました。調査したところ、[Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) を利用することで、手軽にローカルサーバを独自ドメインで公開できることが分かりました。

流れは [Create a locally-managed tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/local-management/create-local-tunnel/) に書かれているので、こちらのページを参照していただくのが良いと思いますが、備忘録としてまとめたので参考になれば幸いです。

## 設定方法と実行方法
### 初期設定

インストール方法は、[Downloads · Cloudflare Zero Trust docs](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/)を参照してください。

[cloudflared](https://github.com/cloudflare/cloudflared) は、Cloudflare ネットワークからオリジンサーバへのトラフィックをプロキシするデーモンです。

```bash
cloudflared tunnel login
cloudflared tunnel create ${TUNNEL_NAME}
cloudflared tunnel route dns ${TUNNEL_NAME} ${HOSTNAME}
```

余談ですが、こちらのデーモンはGo 実装でした。
プロキシ部分の大まかな実装は [proxy/proxy.go](https://github.com/cloudflare/cloudflared/blob/2025.6.0/proxy/proxy.go) にまとめられているので、興味のある方は参照してみると面白いかもしれません。

### 設定ファイルの設置

他のオプションも利用したい場合は、[Configuration file · Cloudflare Zero Trust docs](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/local-management/configuration-file/) を参照してください。

前節の `$ cloudflared tunnel create ${TUNNEL_NAME}` を実行すると、トンネルのUUIDと、クレデンシャルファイルが出力・生成されます。通常は .cloudflared ディレクトリ内に <UUID>.json という名前で生成されているはずです。これらの情報を含む `config.yml` を以下のように作成します。

```yaml
tunnel: '' # tunnel 用に払い出された UUID
credentials-file: '' # 生成された credential file(JSON) へのパス

ingress:
  - hostname: api.task4233.dev # 利用したいサブドメイン用のhostname
    service: http://localhost:5050
  - hostname: www.task4233.dev
    service: http://localhost:3000
  - service: http_status:404
```

### 実行

```bash
cloudflared tunnel --config=config.yml run ${TUNNEL_NAME}
```

### 補足
#### Failed to create record (hostname) with err An A, AAAA, or CNAME record with that host already exists

実行時に以下のようなエラーが出た場合[^1]、すでに登録されたレコードを利用しているはずです。メッセージに書かれているように、手動でダッシュボードから削除すれば解決できます。

> Failed to add route: code: 1003, reason: Failed to create record (hostname) with err An A, AAAA, or CNAME record with that host already exists. For more details, refer to <https://developers.cloudflare.com/dns/manage-dns-records/troubleshooting/records-with-same-name/>.

[^1]: 雑にサブドメインを発行して放置している人あるある、部屋も散らかってがち

#### キャッシュが悪さをしているケース

ローカルコードを変更しても変更が反映されない場合があります。そういった場合、キャッシュが悪さをしている可能性があります。

この場合、Caching > Configuration > Purge Cache > Custom Purge で Hostname (サブドメイン) ごとキャッシュを削除することが可能です。

これで解決する場合、キャッシュコントロールに関する設定を追加すると良いと思います。

## おわりに
大抵の設定方法は、[Locally-managed tunnels](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/local-management/) から辿れます。
必要に応じて参照してください。