本文へ移動
購読
GUIDE#infra更新 2026.09.24

Cloudflare Tunnel(cloudflared)で自宅サーバーをポート開放なしで公開する

cloudflaredで自宅サーバーからCloudflareへ外向きの接続を張り、ポート開放なしで独自ドメイン公開。Quick Tunnel、ingress設定、Docker運用、Accessでの認証まで。

level
初級
time
約30分
stack
cloudflared 2026.9 / Ubuntu 24.04 / Docker Compose
author
@ryoma_t
// 前提条件
Linuxサーバーへのsudo権限 / 名前付きトンネルにはCloudflareのアカウントとCloudflareで管理するドメイン
cloudflaredのバージョンとconfig.ymlのingressルールを検証し、ホスト名ごとの転送先を確認したターミナル
画像:nagoya.dev(実際の画面・実行結果をもとに作成)

cloudflaredとは、Cloudflare Tunnelのコネクタとして動く常駐プログラムで、自宅サーバーからCloudflareのデータセンターへ外向きの暗号化接続を張り、その接続を通してインターネットからのリクエストを受け取ります。ルーターのポート開放も固定IPも要らず、IPv4の着信ができないMAP-EやDS-Liteの回線でも、自宅のWebサービスを独自ドメインで公開できます。

この記事では、アカウントなしで1分で試せるQuick Tunnelから始め、独自ドメインでの名前付きトンネル(config.ymlで管理する方式と、ダッシュボードのトークンで動かす方式)、Docker Composeでの運用、Cloudflare Accessでの認証の前置きまでを、cloudflared 2026.9.1(執筆時点)で実際に動かした結果とあわせて説明します。

この記事でわかること

  • 外向きの接続だけで公開できる仕組みと、TLSがCloudflareで終端されるというトレードオフ
  • アカウント不要のQuick Tunnelの試し方と、その制限
  • 名前付きトンネルの作成(login・create・route dns)とingressルールの検証方法
  • Docker Composeでトークン方式のコネクタを動かし、ヘルスチェックを付ける書き方
  • 502、Error 1033、UDPが通らない回線など、つまずいたときのログの読み方

Cloudflare Tunnelの仕組み:外向きの接続だけで公開できる理由

cloudflaredは起動すると、Cloudflareのエッジ(region1.v2.argotunnel.com と region2.v2.argotunnel.com)へ7844番で接続します。既定はUDPのQUICで、通らなければTCPのHTTP/2に切り替わります。検証環境から接続すると、ログには Registered tunnel connection ... location=nrt10 protocol=quic のように、接続先の拠点(nrtは東京)とプロトコルが記録されました。

公開するホスト名(例:blog.example.com)は、<トンネルのUUID>.cfargotunnel.com へのCNAMEとしてCloudflareのDNSに登録されます。ブラウザはCloudflareのエッジにHTTPSで接続し、エッジはそのリクエストを既に張られているトンネルに流し、cloudflaredが http://127.0.0.1:8080 などのオリジンへ転送します。自宅側から見ると通信はすべて外向きなので、ルーターの着信の設定は一切いりません。

引き換えに受け入れる点が3つあります。1つ目は、TLSがCloudflareで終端されることです。暗号化されているのはブラウザとエッジの間、エッジとcloudflaredの間で、Cloudflareの内部では中身が見える状態になります。2つ目は、Cloudflareに障害があればサイトも止まることです。3つ目は、次の章以降で触れる規約とアップロード上限です。自分と家族だけが管理画面やSSHに入る用途なら、端から端まで暗号化されるTailscaleでのリモート接続の方が向いています。Tunnelは「URLを知っている人に見せるWebサービス」に使うのが基本です。

方式必要なもの設定の置き場所URL向く用途
Quick Tunnelなし(アカウント不要)コマンドの引数だけ起動のたびに変わる *.trycloudflare.com動作確認、一時的なデモ
名前付き(ローカル管理)Cloudflareのアカウントと、Cloudflareで管理するドメインサーバーの config.yml独自ドメイン設定をファイルで管理したい場合
名前付き(リモート管理)同上Cloudflareのダッシュボード独自ドメインDockerで動かす、複数人で管理する場合

cloudflaredのインストール:UbuntuのAPTリポジトリとDockerイメージ

Ubuntu 24.04では、Cloudflareが公開しているAPTリポジトリを追加してインストールします。以下の手順で入った版は2026.9.1でした。パッケージで入れたcloudflaredは自動更新をしない(起動ログにも cloudflared will not automatically update if installed by a package manager と出ます)ので、更新は apt upgrade の流れで行われます。

terminal
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install cloudflared
cloudflared --version
# cloudflared version 2026.9.1 (built 2026-09-11-13:35 UTC)

Docker Hubの公式イメージ cloudflare/cloudflared は、シェルを含まない最小構成(distroless)で、UID 65532の一般ユーザーとして動き、エントリポイントは cloudflared --no-autoupdate です。docker exec -it … sh で中に入って調べることはできないので、状態はログとメトリクスで確認します。

まず試す:アカウント不要のQuick Tunnel

Cloudflareのアカウントがなくても、ローカルのWebサーバーを一時的に公開できます。テスト用のページを 127.0.0.1:8080 で配信し、そのURLを --url に渡すだけです。

terminal
mkdir -p ~/tunnel-test && cd ~/tunnel-test
echo "hello from home server" > index.html
python3 -m http.server 8080 --bind 127.0.0.1 &
cloudflared tunnel --url http://127.0.0.1:8080

数秒で Your quick Tunnel has been created! という枠の中に https://<ランダムな4単語>.trycloudflare.com のURLが表示され、スマホの回線からそのURLを開くと自宅のページが返ってきます。止めるときはCtrl+Cです。スクリプトから使う場合や、同じ端末で続けて確認したい場合は、ログをファイルに出してURLを取り出します。

terminal
cloudflared tunnel --no-autoupdate --url http://127.0.0.1:8080 > quick.log 2>&1 &
sleep 10; grep -o 'https://[-a-z0-9]*\.trycloudflare\.com' quick.log
curl -s --retry 5 --retry-all-errors --retry-delay 3 "$(grep -o 'https://[-a-z0-9]*\.trycloudflare\.com' quick.log | head -1)"
grep -o 'location=[a-z0-9]* protocol=[a-z0-9]*' quick.log

3つ目のcurlが hello from home server を返せば、インターネット側からトンネル経由でローカルのページに届いています。URLのDNSが引けるようになるまで数秒かかることがあるので、--retry を付けています。最後のgrepは、どの拠点にどのプロトコルで接続したかを表示します。

このトンネルは試験用と割り切ってください。起動時のログ自身が、アカウントなしのトンネルには稼働の保証がないと明記しています。URLは起動のたびに変わり、ログの設定一覧を見るとエッジへの接続は1本(ha-connections:1)だけです。そして、URLを知った人は誰でもアクセスできます。管理画面やファイル共有をQuick Tunnelで開くのはやめてください。

名前付きトンネルを作る:login・create・route dns

独自ドメインで常時公開するには名前付きトンネルを作ります。前提は、ドメインのネームサーバーをCloudflareに向けてあることです(無料プランで構いません)。まずアカウントに対する認証を行います。

terminal
cloudflared tunnel login
cloudflared tunnel create home
cloudflared tunnel route dns home blog.example.com
cloudflared tunnel route dns home photos.example.com

tunnel login はURLを表示するので、ブラウザで開いて対象のドメイン(ゾーン)を選びます。完了すると ~/.cloudflared/cert.pem が保存されます。tunnel create home はトンネルを作り、~/.cloudflared/<UUID>.json に資格情報(トンネルの秘密鍵)を書き出します。tunnel route dns は、指定したホスト名をトンネルへのCNAMEとして登録します。

cert.pem はアカウントのトンネルとDNSを操作できる強い権限を持つので、トンネルを作り終えたら公開サーバーには残さず、作業用のPCで保管してください。<UUID>.json は、持っていれば誰でもこのトンネルのコネクタになれる秘密情報です。パーミッションを600にし、バックアップに含める場合も暗号化してください。

続いて、サービスとして動かすための設定を /etc/cloudflared/ に置きます。UUIDは tunnel create の出力に表示された値に置き換えてください(以下の値は例です)。

terminal
sudo mkdir -p /etc/cloudflared
sudo cp ~/.cloudflared/6ff42ae2-765d-4adf-8112-31c55c1551ef.json /etc/cloudflared/
sudo chmod 600 /etc/cloudflared/*.json
/etc/cloudflared/config.yml
tunnel: 6ff42ae2-765d-4adf-8112-31c55c1551ef
credentials-file: /etc/cloudflared/6ff42ae2-765d-4adf-8112-31c55c1551ef.json

ingress:
  - hostname: blog.example.com
    service: http://127.0.0.1:8080
  - hostname: photos.example.com
    service: http://127.0.0.1:2283
    originRequest:
      connectTimeout: 10s
  - hostname: ssh.example.com
    service: ssh://127.0.0.1:22
  - service: http_status:404
terminal
cloudflared tunnel --config /etc/cloudflared/config.yml ingress validate
cloudflared tunnel --config /etc/cloudflared/config.yml ingress rule https://photos.example.com/api/assets
cloudflared tunnel --config /etc/cloudflared/config.yml ingress rule https://unknown.example.com/
sudo cloudflared service install
sudo systemctl status cloudflared

ingress validate はファイルの文法とルールの並びを検査し、問題がなければ OK を返します。ingress rule は、指定したURLがどのルールに一致するかを表示するコマンドで、上の例では Matched rule #1 と service: http://127.0.0.1:2283 が返り、どのホスト名にも一致しない unknown.example.com は、最後の受け皿である Matched rule #3(service: http_status:404)になりました。番号は0から数えます。どちらもネットワークに接続せずに実行できるので、本番に反映する前の確認に向いています。service install にトークンを渡さない場合は、決められた場所から資格情報付きの設定ファイルを探してsystemdのサービスを作ります。

ingressルールの設計

ingressは上から順に評価され、最初に一致したルールが使われます。最後のルールは、ホスト名もパスも指定しない「すべてに一致する」ルールでなければなりません。これを省くと、ingress validate が The last ingress rule must match all URLs (i.e. it should not have a hostname or path filter) で失敗します。最後に置く http_status:404 は、DNSだけ登録してルールを書き忘れたホスト名に404を返す受け皿です。

service の書き方転送先使う場面
http://127.0.0.1:8080ローカルのHTTPサーバー一般的なWebアプリ
https://127.0.0.1:8006ローカルのHTTPSサーバー自己署名なら originRequest に noTLSVerify: true
ssh://127.0.0.1:22SSHサーバーCloudflare Accessと組み合わせてSSHを通す
http_status:404固定のステータスを返す最後の受け皿

originRequest はルールごとに付けられるオリジン接続の設定で、よく使うのは、接続待ちの上限を決める connectTimeout、オリジンが特定のHostヘッダーを要求するときの httpHostHeader、自己署名証明書を許す noTLSVerify です。1台のサーバーで多数のサブドメインを扱うなら、ingressの hostname に *.example.com と書き、DNSにもワイルドカードのCNAMEを作ってまとめられます。

Docker Composeで動かす:トークン方式とヘルスチェック

Dockerで動かすときは、ダッシュボード側でトンネルを作る「リモート管理」が扱いやすい方式です。Zero Trustダッシュボードのネットワーク関連のメニューにあるTunnelsの画面でトンネルを作ると、コネクタを起動するためのトークンが表示されます(メニュー名は執筆時点のもので、変わることがあります)。公開するホスト名と転送先も同じ画面で設定します。

~/stacks/tunnel/compose.yaml
services:
  cloudflared:
    image: cloudflare/cloudflared:2026.9.1
    command: tunnel --metrics 0.0.0.0:20241 run
    environment:
      TUNNEL_TOKEN: ${TUNNEL_TOKEN}
    healthcheck:
      test: ["CMD", "cloudflared", "tunnel", "--metrics", "localhost:20241", "ready"]
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped
    networks: [web]

  blog:
    image: nginx:1.30-alpine
    volumes:
      - ./site:/usr/share/nginx/html:ro
    networks: [web]

networks:
  web:

トークンは chmod 600 にした .env に TUNNEL_TOKEN=... として書き、Gitには入れません。cloudflaredは環境変数 TUNNEL_TOKEN をトークンとして読みます。ダッシュボードで設定する転送先は http://blog:80 のようにComposeのサービス名で書きます。ここに http://localhost:80 と書くと、cloudflaredのコンテナ自身を指してしまい502になります。

ヘルスチェックの cloudflared tunnel ready は、メトリクスの /ready を問い合わせて、エッジへの接続が1本以上あれば終了コード0、なければ1を返すサブコマンドです。シェルのないイメージでもcloudflared自身で判定できるので、docker compose ps の表示で接続切れに気づけます。メトリクスの20241番はコンテナ内だけで使い、ports: には書きません。

Cloudflare Accessでログインを前置する

家族の写真共有のように「公開はしたいが、見る人は限りたい」サービスには、Cloudflare Accessで認証を挟みます。Zero TrustダッシュボードのAccessからアプリケーションを追加し、種類はセルフホスト、ドメインに photos.example.com を指定して、許可する人のメールアドレスをポリシーに書きます。ログイン方法を既定のワンタイムPINにすれば、アクセスした人は自分のメールに届くコードを入力するだけで入れます。執筆時点のZero Trust無料プランは50ユーザーまでで、家庭や小さなチームには十分です。

Accessの判定はCloudflareのエッジで行われるので、認証を通っていないリクエストは自宅のサーバーまで届きません。アプリ側にログイン機能があっても、その脆弱性を突かれる前に門前払いできる点が利点です。SSHも同じ仕組みで通せます。クライアント側にもcloudflaredを入れ、~/.ssh/config に次のように書くと、初回の接続時にブラウザでAccessのログインが求められます。

~/.ssh/config(クライアント側)
Host ssh.example.com
    ProxyCommand cloudflared access ssh --hostname %h

Proxmox VEの8006番、TrueNASやルーターの管理画面、Portainerのように、奪われるとLAN全体を失う画面はTunnelで公開しないでください。どうしても外から触る必要があるなら、少なくともAccessでメール認証と多要素認証を前置きし、ポリシーの対象を自分のアドレスだけに絞ります。

公開してよいもの・避けるもの:規約・上限・ログのIP

Cloudflareのプロキシを通るリクエストには、プランごとの本文サイズの上限があり、執筆時点では無料プランで1リクエストあたり100MBです。これを超えるアップロードはCloudflareが413で拒否し、自宅のサーバーには届きません。分割アップロードに対応したNextcloudは問題になりにくい一方、スマホの長い動画をそのまま送るタイプのアプリでは失敗します。

動画配信にも注意が必要です。規約面では、CloudflareのCDNに関する条項が有料の専用サービスを使わない動画や大きなファイルの大量配信を制限しており(執筆時点、規約本文で確認してください)、Jellyfinのようなメディアサーバーを家族で見るためにTunnelを使うのは避けるべき使い方です。Jellyfinで自宅メディアサーバーを構築するガイドでは、外からの再生にTailscaleを使う構成にしています。

オリジン側のアクセスログには、すべての接続元が 127.0.0.1(cloudflared)として記録されます。本当のクライアントIPは CF-Connecting-IP ヘッダーに入っているので、cloudflaredの後ろにnginxを置く場合は、次の2行でログとアクセス制限に反映させます。前段が同じホストのcloudflaredだけであることが前提の設定です。

/etc/nginx/conf.d/realip-cloudflared.conf
set_real_ip_from 127.0.0.1;
real_ip_header CF-Connecting-IP;

nginx側のヘッダー設定の考え方はnginxのリバースプロキシ構築ガイドを参照してください。

つまずきポイント

502 Bad Gatewayになる

cloudflaredのログに Unable to reach the origin service. The service may be down or it may not be responding to traffic from cloudflared: dial tcp 127.0.0.1:8080: connect: connection refused が出ていれば、転送先のポートで何も待ち受けていません。オリジンのプロセスとポート番号を確認します。Dockerで動かしている場合は、転送先を localhost と書いていないかを確かめ、サービス名に直します。

ブラウザにError 1033が表示される

Cloudflare Tunnel errorを示す1033は、DNSはトンネルを指しているのに、そのトンネルに接続中のコネクタが1つもない状態です。sudo systemctl status cloudflared でプロセスが動いているか、Dockerならトークンが正しく渡っているかを確認し、cloudflared tunnel info home でコネクタが表示されるかを見ます。

Quick Tunnelが404しか返さない

/etc/cloudflared/config.yml や ~/.cloudflared/config.yml が残っていると、Quick Tunnelもそのingressルールを読み込みます。検証中に名前付きトンネル用の設定を置いたまま --url で起動したところ、どのホスト名にも一致せず最後の http_status:404 が返り続けました。試すときは、設定ファイルのないユーザーで実行するか、一時的に設定ファイルを退避します。

社内やホテルのネットワークでだけ接続できない

新しい版のcloudflaredは、起動時に CONNECTIVITY PRE-CHECKS という表を出し、DNS、UDP(QUIC)、TCP(HTTP/2)、APIへの到達性を1行ずつ判定します。UDPの行がPASSにならないネットワークでは、config.ymlに protocol: http2 を書くか、起動オプションに --protocol http2 を付けてTCPで接続させます。検証では、このオプションでログが Initial protocol http2 に変わり、protocol=http2 で接続が登録されました。

動作確認

名前付きトンネルが動いているかは、コネクタの状態、ingressの一致、実際の応答の3点で確かめます。

terminal
cloudflared tunnel list
cloudflared tunnel info home
cloudflared tunnel --config /etc/cloudflared/config.yml ingress rule https://blog.example.com/
curl -sI https://blog.example.com/ | grep -i -E '^(HTTP|server|cf-ray)'

tunnel info に接続中のコネクタとエッジの拠点が表示され、curlの応答ヘッダーに server: cloudflare と、末尾が拠点コード(東京なら -NRT)の cf-ray が含まれていれば、リクエストはCloudflare経由で届いています。検証で使ったQuick Tunnelのcurlでは、HTTP/2 200、server: cloudflare、cf-ray: …-NRT が返りました。

サーバー上では、メトリクスの /ready でも接続数を確認できます。前述のQuick Tunnelでは {"status":200,"readyConnections":1,"connectorId":"…"} が返りました。名前付きトンネルを --metrics 127.0.0.1:20241 付きで起動しておけば、同じURLを監視ツールから定期的に叩けます。

terminal
curl -s http://127.0.0.1:20241/ready
cloudflared tunnel --metrics 127.0.0.1:20241 ready; echo $?

よくある質問

Cloudflaredとは何ですか?

Tunnelのコネクタとなるプログラムで、サーバーからCloudflareのエッジへ外向きの接続を張り、その接続でリクエストを受け取ります。ほかにも、SSHなどをCloudflare Access経由で使うためのクライアント(cloudflared access)の機能を持ち、Linux、Windows、macOS、Docker向けに配布されています。

Cloudflareとは何ですか?

世界中にデータセンターを持ち、DNS、CDN、WAF、DDoS対策、ゼロトラスト型のアクセス制御などを提供する米国の企業・サービスです。Webサイトの手前に立つリバースプロキシとして使われることが多く、Cloudflare Tunnelはその入口に自宅や社内のサーバーを外向きの接続でつなぐ機能です。

クラウドフレアはなぜ無料なのですか?

無料なのはFreeプランの範囲の機能で、Cloudflareは有料プランと企業向け契約で収益を得ています。Tunnel自体は無料で使え、Accessは執筆時点で50ユーザーまで無料です。ただし無料プランにもアップロードサイズの上限や、動画などの大容量配信に関する規約上の制限があるので、用途が合うかを先に確認してください。

まとめ

Cloudflare Tunnelは、着信の設定ができない回線でも独自ドメインでWebサービスを公開できる手段で、Quick Tunnelなら1分で動作を確かめられます。常時公開するときは名前付きトンネルにして、ingress validate と ingress rule で振り分けを確かめてから反映し、見る人を限りたいサービスにはAccessを前置きします。管理画面と動画配信はTunnelに載せず、Tailscaleなど端から端まで暗号化される経路に分けるのが安全な線引きです。

次に読む

$ subscribe --weekly
毎週月曜、企業ITとホームラボの要点を5分で。