TechQuant Blog

Cloudflare Tunnelで自宅サーバーを安全公開:ポート開放なしの実践手順

7分で読める

深夜2時。寝落ちしかけた僕の手元のRaspberry Pi 5で、ふと思いついた。「このダッシュボード、外から見られるようにしたい」と。ルーターのポートを開ける気はさらさらない。自宅のIPを晒したくもない。動的DNSの設定で過去に3回詰んだ経験がある。

そこで導入したのがCloudflare Tunnelだった。半年運用して、ポート開放なしで自宅のRaspberry Piをインターネット越しに扱えるようになった。本記事では、実際に自宅のRaspberry Pi 5上でcloudflaredを常駐させ、Zero Trustで制限をかけるまでの手順と、運用で躓いたポイントを書き残しておく。

Cloudflare Tunnelとは(超ざっくり)

Cloudflareが提供する、アウトバウンド接続のみでサーバーを外部公開する仕組み。自宅側からCloudflareのエッジに常時接続(gRPC over HTTPS)を張り、外からのリクエストはCloudflare経由でそのトンネルを通って入ってくる。

つまりルーターの穴あけ(ポートフォワーディング)は一切不要。固定IPも要らない。CGNATの下でも動く。料金は無料プランでもほぼ使える。これが強烈に刺さった。

  • ポート開放不要 → 自宅ルーターが無防備にならない
  • 公開IPが露出しない → DDoSの標的にされにくい
  • TLS終端はCloudflare側 → 証明書の更新で悩まない
  • Zero Trustでアクセス制御 → Google認証やメールOTPで簡単にゲートがかけられる

以前のやり方との比較

以前、自宅サーバー運用の取り組みをRaspberry Pi 5を家庭内サーバーとして運用する実践Tipsでまとめた。そこではTailscaleとReverse Proxyで閉じていたが、身内以外にも共有したい場面が出てきて、公開の必要が生じた。Cloudflare Tunnelはその隙間を埋めてくれる。

前提環境と準備物

手元の構成はこんな感じ。

項目内容
ハードRaspberry Pi 5 (8GB) + Ubuntu Server 24.04 arm64
公開対象ローカルで動くFlaskアプリ(ポート8080)
ドメインCloudflareにネームサーバーを委任済みの独自ドメイン
CloudflareプランFree(Zero Trustも50ユーザーまで無料)

独自ドメインは必須。Cloudflareにネームサーバーを向けていれば、サブドメインとしてトンネル先を割り当てる形になる。僕はもう何年もお名前.comで管理している独自ドメインを使い回していて、Cloudflareへのネームサーバー委任もダッシュボードからコピペで済んだ。

cloudflaredのインストールと初期設定

ARM64版のバイナリをCloudflareの公式リポジトリから取得する。aptで入れるのが一番楽。

curl -L --output cloudflared.deb \
  https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64.deb
sudo dpkg -i cloudflared.deb
cloudflared --version

次にCloudflareにログインして認証トークンを取る。

cloudflared tunnel login

ブラウザが立ち上がって(SSH越しの場合は表示されたURLを別端末で開く)、利用するドメインを選ぶとcertファイルが~/.cloudflared/cert.pemに保存される。ここで一度、cert.pemの保存場所を見失って30分溶かした。SSH先のホームディレクトリ直下に置かれるのだが、rootで実行していると/root/.cloudflared/に行ってしまうので注意。

トンネルを作る

cloudflared tunnel create techquant-home
# Created tunnel techquant-home with id: abcd1234-...
cloudflared tunnel list

作成すると~/.cloudflared/<UUID>.jsonが生成される。これが認証情報の実体。間違ってgitに上げないよう、gitignore必須。

config.ymlを書いてトンネルの向き先を定義する

ここが一番つまづきやすいパート。~/.cloudflared/config.ymlに以下のように書く。

tunnel: abcd1234-....
credentials-file: /home/pi/.cloudflared/abcd1234-....json

ingress:
  - hostname: dash.example.com
    service: http://localhost:8080
  - hostname: api.example.com
    service: http://localhost:5000
  - service: http_status:404

ingressはリスト順に評価されるので、ワイルドカード的なhttp_status:404は必ず最後に置く。これを忘れると意図しないトラフィックを全部通してしまう。

続いてDNSレコードを自動で張る。

cloudflared tunnel route dns techquant-home dash.example.com
cloudflared tunnel route dns techquant-home api.example.com

Cloudflare側にCNAMEが自動生成され、オレンジ雲がオンになっている(=プロキシ経由)状態になる。

systemdで常駐させる

cloudflaredはフォアグラウンドでも動くが、常駐させないと再起動で止まる。

sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
sudo systemctl status cloudflared

service install~/.cloudflared/配下のconfigを/etc/cloudflared/にコピーする。コピー元が最新版でないと古い設定が残り続ける。ここは定期メンテで更新が要る部分。設定変更したら、

sudo cp ~/.cloudflared/config.yml /etc/cloudflared/config.yml
sudo systemctl restart cloudflared

を毎回やる。cronからsystemd timerへの移行記事で書いた通り、systemd運用に慣れておくとこういう常駐プロセスの扱いが楽になる。

ヘルスチェックをcronで回す

トンネルは稀に切れる。自宅回線が再接続された後、まれにcloudflaredが復旧しない現象を経験した。対策として5分ごとにヘルスチェックを回している。

*/5 * * * * curl -fsS https://dash.example.com/healthz \
  || systemctl restart cloudflared

乱暴だが、これで半年間ダウンタイムほぼゼロで運用できている。

Zero Trustでアクセスを絞る

デフォルトでは誰でもURLを叩けてしまう。管理用のダッシュボードを公開するなら、Google認証やメールOTPで絞りたい。Cloudflare Zero Trust(旧Access)の出番。

  1. Cloudflare Dashboard → Zero Trust → Access → Applications → Add an application
  2. Self-hostedを選び、dash.example.comを入力
  3. Identity providerにGoogle(または内蔵のメールOTP)を追加
  4. Policy: 「特定のメールアドレスのみ許可」を設定

これで、dash.example.comに来るとCloudflareの認証画面が挟まり、許可したメールアドレスでGoogleログインしないと先に進めなくなる。社内ツール感覚で自宅サーバーを公開できるのは、正直ちょっと感動した。

注意:Zero TrustはFreeプランでも50ユーザーまで使えるが、機能の一部はPaidのみ。SSO連携など高度な用途なら有料プランを検討する。

ハマったポイント集

1. WebSocketが切れる

Flask-SocketIOを載せたアプリでWebSocket接続が頻繁に切れた。config.ymlの対象ingressにnoTLSVerify: trueではなく、タイムアウト設定を入れて解決。

ingress:
  - hostname: dash.example.com
    service: http://localhost:8080
    originRequest:
      connectTimeout: 30s
      noHappyEyeballs: true
  - service: http_status:404

2. Basic認証が二重で面倒

アプリ側で既にBasic認証をかけていると、Zero Trustと合わせて二重認証になる。Zero Trustに寄せるなら、アプリ側の認証は外すかサービスアカウント扱いにする。

3. トンネルが1日1回瞬断する

これは自宅回線(v6プラス)のIPv6アドレスが変わるタイミング。cloudflaredはv4で繋ぎ直せるので実害はないが、厳密にはログを見ると3-5秒のギャップがある。対策は諦めている。ここは正直まだ試行錯誤中。

セキュリティ面の考え方

ポート開放が不要という点で一段安全になるが、油断は禁物。

  • Zero TrustのPolicyは必ず「allow specific email」形式にする。「everyone」にしない
  • credentials-fileは必ずchmod 600。漏れると誰でもそのドメインに偽サーバを立てられる
  • 公開するサービスは最小限に。ローカルの8080だけ通し、SSHやデータベースは絶対にingressに書かない
  • Cloudflare側のログ(Analytics → Tunnels)を週1で見る習慣をつける

自動売買のような本番サービスを載せる場合は、さらに一段階踏み込んだ層を挟むのが無難。Raspberry Piで自動売買を運用するBookでも触れたが、金に関わる系統は公開しない運用が安全。

本格的な公開用途にはVPSも選択肢

自宅回線に載せ続けることにこだわりがなければ、最初からVPSで完結させる手もある。電気代と回線の安定性を考えると、月数百円のVPSに寄せたほうが結果的に安く上がることも。ちなみに自分は軽めのサービスはお名前.comの高性能VPSに載せ替えて、自宅のPi 5は開発&検証専用にしている。用途で分けるのが結局ラクだった。

半年使った感想

Cloudflare Tunnelは、自宅サーバーを公開するときに考えることを大幅に減らしてくれた。ポート開放、DDNS、SSL証明書、IPアドレスの露出。これらの悩みが一気に消える。

一方、完全に透過というわけでもない。ingressの順序、config.ymlの配置場所、v6の瞬断、WebSocketのタイムアウト。小さな罠が点在していて、ちゃんと動かすには半日くらい向き合う必要がある。でも一度軌道に乗せれば、運用の手離れはかなり良い。

ポートを開ける気力はないが、自宅で動かしてるものを軽く外に見せたい。そのニーズにはこれ以上ない解だと思う。

本記事は筆者の自宅環境(Raspberry Pi 5 + Ubuntu 24.04)での検証・運用に基づく情報です。Cloudflareの仕様は変わる可能性があり、公開用途の運用は自己責任でお願いします。認証・アクセス制御は必ず適切に設定してください。