# サーバーの 80/443 を閉じたまま Web を公開する：既存の業務システムに Cloudflare Tunnel と Access を導入する

- 公開日: 2026-11-03
- 更新日: 2026-11-03
- URL: https://systemdesignworks.co.jp/news/%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%AE80%E3%81%A8443%E3%82%92%E9%96%89%E3%81%98%E3%81%9F%E3%81%BE%E3%81%BEWeb%E3%82%92%E5%85%AC%E9%96%8B%E3%81%99%E3%82%8B%EF%BC%9A%E6%97%A2%E5%AD%98%E3%81%AE%E6%A5%AD%E5%8B%99%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%81%ABCloudflare%E3%81%AETunnel%E3%81%A8Access%E3%82%92%E5%B0%8E%E5%85%A5%E3%81%99%E3%82%8B
- 発行: 株式会社システムデザインワークス

> Web サーバーなのに、ファイアウォールでは 80 も 443 も閉じている。それでも利用者は今までどおりブラウザで使える。お客様の業務システム（PHP、AWS）を、Cloudflare Tunnel と Cloudflare Access でそういう状態にしました。アプリのコードは変えず、追加の月額費用はゼロです。PHP2Shell のような攻撃をどこで受け止めるのか、閉じるまでの4段階と戻し方、「人が使わない経路」の通し方、設定で引っかかった点を公開します。

こんにちは！システムデザインワークスの山岸です。

**Web サーバーなのに、ファイアウォールでは Web の入口である 80 番（HTTP）も 443 番（HTTPS）も閉じている。それでも利用者は今までどおりブラウザで使えている。** お客様の業務システムを、そういう状態にしました。

使ったのは Cloudflare Tunnel（サーバーから Cloudflare へ外向きに張る暗号化された接続）と Cloudflare Access（メールで届く認証コードによる前段認証）です。アプリのプログラムは1行も変えず、追加の月額費用はゼロ、設定から切替完了まで実質2日。サーバーの IP アドレスを知られても、80 / 443 には何も待ち受けていないので、つなぐ先がありません。10月に公開された PHP 本体の重大な脆弱性「PHP2Shell」のような攻撃に対しても、サーバーに届く前に Cloudflare 側で受け止める層が3つあります。

[前回の記事](/news/相次ぐ個人情報漏えいに学ぶセキュリティ対策：50人以下の会社なら無料で始めるゼロトラスト)で「既存の管理画面は作り直さなくても、手前に Cloudflare Access を置く5ステップで始められる」と書きました。本記事はその実践編です。**何のために 80 / 443 を閉じるのか、閉じたまま公開できる仕組み、閉じるまでの4段階と戻し方、その前に洗い出した「人が使わない経路」、そして設定で引っかかった点**を中心に書きます。

なお、本記事は2026年11月3日時点の設定画面と公式ドキュメントに基づいています。お客様を特定できる情報は伏せています。

---

**この記事はこんな方に向けて書いています**

- 自社のサーバーの IP アドレスに、世界中から直接つなげる状態で業務システムや管理画面を運用している経営者、情報システム担当の方
- PHP2Shell のニュースを見て、自社の PHP で動くシステムが大丈夫か気になった方
- 「ログイン画面の手前にもう1枚守りを置きたいが、古いシステムには手を入れたくない」という方
- Cloudflare Tunnel / Access の導入を検討していて、既存システム特有の落とし穴を先に知りたいエンジニアの方

技術用語は本文で一言ずつ補足します。

---

## 背景・課題：URL を知っていれば誰でもログイン画面まで届く

対象は、お客様が社外から利用している業務システムです。PHP で書かれ、AWS 上の仮想サーバー（Apache）で動いています。利用者は十数名で、ログインは ID とパスワードのみ。多くの業務システムと同じく、URL を知っていれば誰でもログイン画面まで届く状態でした。

今回はシステム全体のセキュリティ強化をご依頼いただいたのですが、プログラムの改修には時間がかかります。そこで、**改修を待たずに入口だけ先に固める**ことにしました。お客様からの要望は次の2点です。

- ログインのたびに、登録したメールアドレスへ認証コードを届けてほしい
- 利用者ごとに、許可したメールアドレスだけが使えるようにしてほしい

前段に置く仕組みとして Cloudflare Access を選びました。理由は[前回の記事](/news/相次ぐ個人情報漏えいに学ぶセキュリティ対策：50人以下の会社なら無料で始めるゼロトラスト)で書いたとおり、50ユーザーまで無料で、メールのワンタイム PIN（使い捨ての認証コード。有効期限は10分）に対応しているためです。お客様側には SSO に使える ID 基盤がなかったので、メールアドレスだけで始められることが決め手でした。

もう1つ、このシステムには「人が使う画面」以外の経路が4つあります。**メール送信機能**の送信結果を Amazon SNS（AWS の通知サービス）から受け取る webhook（外部サービスからの自動通知を受け取る URL）、メールに記載したリンクから開く**ログイン不要のページ**、Let's Encrypt（無料の証明書発行サービス）による証明書の自動更新（認証局がサーバー上の特定の URL を確認しに来る）、そして定期実行のバッチ（cron）です。入口を Cloudflare だけにすると、これらもすべて Cloudflare を通ることになり、何もしなければ認証画面に弾かれます。**アプリを変えずに、これらを壊さずに通す**ことが、今回の設計の中心になりました。

## 何のために 80 / 443 を閉じるのか：PHP2Shell を例に

切替作業の直前、2026年10月21日に PHP 本体の重大な脆弱性が2件公開されました。CVE-2026-109618 と CVE-2026-109619、通称「PHP2Shell」です。認証なしのリクエストだけでサーバー上でコードを実行されうるもので、深刻度は CVSS 9.2（脆弱性の深刻度を10点満点で表す指標。Critical）。Apache に PHP を組み込んで動かす mod_php 構成がもっとも危険とされています。

影響範囲も広く、PHP 5.4 以降のほぼすべてのバージョンが対象だとする分析（Wiz）もあります。修正版の公開前から攻撃の試行が観測され、米 CISA は10月26日に「悪用が確認された脆弱性」のカタログに加えました。PHP で動く Web システムなら、他人事ではありません。

この種の脆弱性で押さえておきたいのは、**攻撃者はドメイン名だけでなく、IP アドレスの範囲も機械的にスキャンする**ことです。80 / 443 が開いていれば、サイトの URL を誰にも教えていなくても、サーバーには攻撃リクエストが届きます。「URL を知っていれば誰でもログイン画面まで届く」どころか、URL を知らなくても PHP まで届くのです。

今回の構成では、PHP2Shell のような攻撃を3つの層で受け止めます。

| 層 | 何が起きるか |
| --- | --- |
| 1. 80 / 443 を閉じる | IP アドレスをスキャンしても、つなぐ先がない。攻撃リクエストはサーバーに届かない |
| 2. Cloudflare Access | ドメイン名経由で来ても、認証なしのリクエストは PHP に渡らない（認証を外した3つのパスを除く）。アプリや PHP 本体に脆弱性があっても、認証画面の手前で止まる |
| 3. Cloudflare の WAF 管理ルール | 認証を外した webhook などの経路にも、Cloudflare が保守するルールが効く。PHP2Shell では、脆弱性公開の翌日 10月22日に、Free プラン向けのルールセットでも既存の PHP 向けルールがこの CVE を対象にすると明記された |

3つ目は補足しておきます。Cloudflare の Free プランには「Cloudflare Free Managed Ruleset」という、広く悪用される重大な脆弱性に絞った管理ルールが最初から有効になっています。PHP2Shell では、10月22日の緊急リリースで「PHP Remote Code Execution Attack」という既存の7本のルール（いずれも Block）にこの2件の CVE が明記され（検知ロジック自体は既存のもの）、その後10月28日と30日に、セキュリティ研究者の報告を受けて検知の更新が続いています。自分たちが寝ている間に、入口のルールが更新されている状態です。

ただし、WAF は「サーバー側の更新が終わるまでの時間を稼ぐ層」です。PHP 本体を修正版に更新する作業は別に必要で、Cloudflare を通しているから更新しなくてよい、ということにはなりません。それでも、脆弱性が公開されてから自分たちがサーバーを更新するまでの数日間、入口で止めてくれる層があるかどうかは大きな違いです。

この3層のうち、1つ目を作るのが Cloudflare Tunnel です。

## なぜ 80 / 443 を閉じたまま公開できるのか：Cloudflare Tunnel の仕組み

### サーバーから外向きに接続を張る

通常の Web サーバーは、80 / 443 で外からの接続を待ち受けます。Cloudflare Tunnel では逆に、サーバー内の cloudflared というプロセスが Cloudflare へ**外向きに**接続を張り、Cloudflare に届いたリクエストはその接続の中を通ってサーバーへ渡されます。サーバー側で待ち受けるポートがないため、ファイアウォールで 80 / 443 を閉じたままサイトを公開できます。

```mermaid
flowchart LR
    U["利用者のブラウザ"] --> CF["Cloudflare<br/>Access（メール認証コード）+ WAF"]
    SNS["Amazon SNS<br/>（外部サービスからの通知）"] --> CF
    CF -->|"Tunnel（サーバーから張る外向きの接続）"| CD["cloudflared<br/>（サーバー内の常駐プロセス）"]
    CD --> AP["Apache + PHP<br/>（127.0.0.1 で受ける）"]
    X["サーバーの IP への直接アクセス"] -.->|"80/443 は閉鎖。つなぐ先がない"| AP
    style CF fill:#e8f0fe,stroke:#1a56db
    style X fill:#fde2e2,stroke:#c00
```

### Proxy（オレンジ雲）だけでは足りない理由

Cloudflare の Proxy を通すだけでも、DNS からはサーバーの IP が見えなくなります。しかし、過去の DNS の記録や設定ミスなどから IP が分かってしまえば、直接つないで Access を素通りできます。ファイアウォールで 80 / 443 を Cloudflare の IP 一覧だけに絞れば防げますが、IP 一覧が変わったときに追従する保守が残ります。Tunnel なら、そもそも開いているポートがありません。3つの方式を並べると次のようになります。A レコードとは、ドメイン名にサーバーの IP アドレスを直接結びつける DNS の設定です。

|  | A レコードで直接公開 | Cloudflare Proxy ＋ ファイアウォールを Cloudflare の IP に限定 | Cloudflare Tunnel |
| --- | --- | --- | --- |
| サーバーで開ける受信ポート | 80 / 443 を全世界に | 80 / 443 を Cloudflare の IP だけに | なし（cloudflared が外向きに接続） |
| サーバーの IP が分かったとき | 直接つながれ、Access を素通りされる | Cloudflare 以外からは届かない | つなぐ先がない |
| 保守 | — | Cloudflare の IP 一覧が変わったら更新 | 不要 |
| サーバー側の証明書 | 公開用に必要 | Cloudflare との間に必要。暗号化モード「フル（厳密）」のとき | cloudflared と Apache の間だけの内部用 |

今回は、この表の左から右へ段階的に進めました。

### 80 / 443 を閉じるまでの4段階

一度に全部やらず、各段階に「サーバーの 80 / 443 がどうなるか」と「戻し方」を決めてから進めました。お客様に影響があるのは第2段階だけで、ログイン時に認証コードの入力が必要になります。ほかの段階は、利用者から見て何も変わりません。

| 段階 | 内容 | サーバーの 80 / 443 | 戻し方 |
| --- | --- | --- | --- |
| 1. DNS の移管 | ドメインのネームサーバー（ドメイン名の問い合わせ先）を Cloudflare に変える。この時点では Cloudflare を経由させない（DNS only） | 全世界に公開（変化なし） | ネームサーバーを元に戻す |
| 1.5. テスト用ホスト名で検証 | Tunnel 経由のテスト用サブドメインを作り、Access と Bypass、セッションの挙動を確かめる | 全世界に公開（変化なし） | テスト用ホスト名を消す |
| 2. Proxy ON | 本番のホスト名を Cloudflare 経由（Proxied）にし、Access と WAF を有効化 | Cloudflare の IP だけに限定 | ファイアウォールを開けてから DNS only に戻す |
| 3. Tunnel へ移行 | A レコードを消して Tunnel のルートに置き換える | **閉鎖** | 80 / 443 を Cloudflare の IP に限定して開け、A レコードを作り直す |
| 4. 後片付け | 旧 DNS ゾーンの削除、テスト用ホスト名の削除 | 閉鎖のまま | — |

第2段階の戻し方に「**ファイアウォールを開けてから**」と書いたのには理由があります。80 / 443 を Cloudflare の IP に絞った状態で先に DNS only へ戻すと、利用者のブラウザはサーバーへ直接つなぎに行き、誰もつながらなくなります。戻す順番まで先に決めておくのが、段階を分ける意味です。

実際には第1段階を夜に行い、翌日（祝日）に第1.5〜第3段階まで進めました。第2段階が安定してから別日に第3段階という計画でしたが、確認項目がすべて通ったので、同じ夜に前倒ししています。

## 設計の要点：「人が使わない経路」を先に洗い出す

80 / 443 を閉じるとは、Cloudflare を通らない経路をなくすことです。つまり、これまでサーバーに直接届いていた「人が使わない経路」も、すべて Cloudflare 経由で通す必要があります。

### Cloudflare 経由にして影響を受ける処理

最初にやったのは、アプリのコードと cron を読んで、HTTP を通る処理を全部書き出すことです。

| 処理 | 経路 | 対応 |
| --- | --- | --- |
| メール送信結果の通知 | Amazon SNS → `POST` で webhook の URL へ | Access の Bypass ＋ WAF（通知元だけ Skip、それ以外は Block）＋ SNS の再送回数を増やす |
| ログイン不要の公開ページ | 利用者以外がブラウザで開く | Access の Bypass ＋ レート制限 |
| 証明書の自動更新 | 認証局 → `/.well-known/acme-challenge/*` | Access の Bypass。Tunnel 移行後はブラウザ向けには使われなくなる |
| 定期バッチ | cron の PHP CLI | 影響なし（HTTP を通らない） |
| 検索・ページ送り・CSV 出力 | すべて `POST` | Access のセッション切れで `POST` が認証画面への転送に変わる。事前に挙動を確認 |
| CSV 出力（全件） | 1回のリクエストで生成 | Cloudflare はオリジン（本来のサーバー）の応答を既定で 125秒待って打ち切る（524 エラー）。本番データ量で計測 |

定期バッチは HTTP を通らないので影響がない、と分かるだけでも安心材料になります。逆に、SNS の通知を見落とすと、切替の瞬間から通知が届かなくなります。

### Bypass はパスを指定した別アプリにする

Access では、ホスト全体を守るアプリとは別に、**特定のパスだけを対象にした Self-hosted アプリ（Access で守る対象の設定単位）**を作れます。そこに「Action: Bypass、Include: Everyone」のポリシーを付けると、そのパスだけが認証なしで通ります。1つのアプリに複数のパスをまとめられるので、webhook・公開ページ・証明書更新の3つを1つの Bypass アプリにしました。

同じアプリの中では「より具体的なパスが優先」とドキュメントにありますが、ホスト全体のアプリ（パスなし）とパス指定の別アプリが重なった場合の記述はなかったため、第1.5段階で実際に確かめました。結果は**パスを指定したアプリが優先**です。

### Bypass だけでは「誰でも叩ける」ので WAF で絞る

Bypass にした webhook の URL は、Access を通らない以上、誰でも `POST` できます。そこで Cloudflare の WAF カスタムルール（Free プランで5本まで）を2本重ねました。

1. **通知元だけ Skip**: パスが webhook の URL で、メソッドが `POST`、User-Agent（送信元が名乗るソフト名）が `Amazon Simple Notification Service Agent` のときだけ、カスタムルールをスキップする
2. **それ以外は Block**: パスが webhook の URL なら拒否する

Skip ルールでは「Cloudflare の管理ルール（無料の基本的な保護）」までは外さないようにしました。User-Agent は偽装できるので、Skip の対象は自分のカスタムルールだけに留めるのが安全側です。なお、通知が本物かどうかは本来、アプリ側で SNS の署名を検証して担保するものです。WAF の条件は、それを補う入口側の層と位置づけています。

公開ページのほうは、同じ IP から短時間に連続 `POST` されたら止めるレート制限ルール（Free プランで1本、10秒単位）を付けました。

### SNS の再送回数は切替前に増やしておく

Amazon SNS の HTTPS 配信は、失敗したとき**既定では20秒間隔で3回しか再送せず、約1分で通知を捨てます**。切替作業中に数分でも通知が届かない時間があると、その間の通知は失われます。そこで、SNS 側の配信ポリシーを「最大20回、20秒から600秒まで間隔を広げながら再送（指数バックオフ）」に変え、再送時間の上限であるおよそ1時間、再送が続くようにしました。これは Cloudflare 側ではなく AWS 側の設定で、切替前に済ませておく項目です。

ただし、再送されるのは 5xx や 429、接続失敗のときだけです。Access が認証画面へ転送する 302 や、WAF が拒否する 403 は「正常に届いて断られた」扱いになり、再送されません。つまり再送設定が守ってくれるのは切替の数秒間だけで、Bypass や WAF の設定漏れは守れません。だからこそ、次の章で書くように、本物の通知で確認してから Block を有効にしています。

### 一番重い処理を本番データ量で計測する

Cloudflare 経由にすると、オリジンの応答を既定で 125秒待って 524 エラーになります（延長できるのは Enterprise プランのみ）。このシステムで一番重いのは全件の CSV 出力だったので、本番と同じ件数で計測し、制限内に収まることを確かめました。

## 切替と検証：本番の前にテスト用ホスト名で全部試す

### テスト用ホスト名を Tunnel 経由にした理由

本番のホスト名を Proxied にする前に、テスト用のサブドメインで Access の挙動を確かめたいところです。ところが、A レコードでサーバーの IP を向けると、SSL/TLS の暗号化モードを「フル（厳密）」にしている場合、サーバーの証明書がテスト用のホスト名に対して有効でないため 526 エラーになります。

そこで、先に cloudflared をサーバーに入れ、テスト用ホスト名は Tunnel 経由にしました。Tunnel の公開ホスト名の設定で「No TLS Verify」を ON にし、HTTP Host ヘッダ（どのホスト名宛てかをアプリに伝える情報）を本番のホスト名に書き換えれば、アプリ側は本番と同じ名前で呼ばれたと認識します。本番の第3段階で使う Tunnel を、テストの段階から使い始めた形です。

### curl で経路ごとに確かめる

テスト用ホスト名に本番と同じ構成の Access アプリを作り、次を確かめました。

```typescript
# 通知元を装った POST は Access を通り抜けて 200 が返る
curl -s -o /dev/null -w '%{http_code}\n' -X POST \
  -A 'Amazon Simple Notification Service Agent' -d '{}' \
  https://test.example.com/path/to/webhook.php

# 証明書更新の URL は 404（認証画面への 302 ではない）
curl -s -o /dev/null -w '%{http_code}\n' \
  https://test.example.com/.well-known/acme-challenge/x

# それ以外のパスは認証画面へ 302
curl -s -o /dev/null -w '%{http_code}\n' https://test.example.com/
```

ブラウザでは、許可したメールアドレスで認証コードが届いてログインできること、許可していないアドレスでは「コードを送信」と表示されてもメールが届かないこと（正しく拒否されている）、検索・ページ送り・CSV 出力が動くことを確認しました。

### 本番では「本物の通知」で確かめてから Block を有効にする

WAF の Skip ルールは User-Agent で判定しています。ところが、SNS が本当にその User-Agent で来るのかを、切替前に確かめる手段がありませんでした。

そこで第2段階では、**Block ルールを無効にした状態で Proxied に切り替え**、通知元の仕組みでテスト用の通知を発生させました。AWS には、テスト用の宛先に送ると成功の通知だけが返ってくるメールボックスシミュレータがあり、本番のデータに触れずに本物の SNS 通知を起こせます。Cloudflare のセキュリティイベントで通知元の IP が Skip ルールに一致していることを確認してから、Block ルールを有効にしました。

その後、ファイアウォールで 80 / 443 を Cloudflare の IP 一覧（IPv4・IPv6 の両方）だけに絞り、サーバーの IP への直接アクセスがタイムアウトすることを確かめました。

### Tunnel 化して 80 / 443 を閉じる

第3段階では、A レコードを削除して Tunnel の公開ホスト名に本番のホスト名を設定します。どちらも Proxied なので外から見える IP は変わらず、停止は数秒でした。

経路が本当に Tunnel に変わったかは、適当なクエリ文字列を付けた目印リクエストを送り、Apache のアクセスログにそのリクエストが **127.0.0.1 から**届いていることで確認しました。Proxy 経由の段階では Cloudflare の IP から届いていたものが、Tunnel 経由では cloudflared がローカルから渡す形になります。

確認後、ファイアウォールから 80 / 443 のルールを削除しました。これで、サーバーの IP に直接つないでも何も返ってきません。

```typescript
# 切替後: サーバーの IP に直接つないでも、80 / 443 ともタイムアウトする
curl -m 10 http://203.0.113.10/
curl -m 10 -k https://203.0.113.10/
```

同じ時点で、ドメイン名経由では Access のログイン画面が出て、通知元からの `POST` は 200 で届いています。**ポートを閉じたまま、サイトは公開され続けている**状態です。

### 「毎回コード」は Access の設定だけでは満たせない

お客様の要望は「ログインのたびに認証コード」でした。ところが Access のセッションは、**認証コードを入力した時点から数える固定時間**で、操作しても延びず、アプリ側でログアウトしても消えません。しかも2段構えです。アプリごとの Session duration のほかに、チーム全体の Global session（既定24時間）があり、アプリ側のセッションが切れても Global session が有効なら、コードなしで新しいトークンが発行されます。Global session を長いままにすると、アプリ側だけを短くしても効きません。今回は Global session を「Same as application session timeout」（アプリ側と同じ時間）にしてあり、アプリに設定した2時間がそのまま効いています。

試した範囲では、ブラウザを閉じて開き直すとコードを求められますが、アプリのログアウトボタンを押してセッションの時間内に開き直すと、認証コードなしにアプリのログイン画面まで戻れてしまいます（アプリの ID とパスワードは必要です）。厳密に毎回求めるには、アプリのログアウトと自動ログアウトを `/cdn-cgi/access/logout`（Access のセッションをすべてのアプリについて消す URL）経由にする改修が必要です。これはプログラム改修フェーズで行うことにし、**今回は「アプリを変えない」を優先して切替を先に済ませました**。

### 設定で引っかかった5つ

どれも公式の手順には出てこない、画面で実際に起きたことです。

| # | 引っかかった点 | 対処 |
| --- | --- | --- |
| 1 | ゾーン追加時の DNS スキャンで取り込まれた A レコードが **Proxied** になっていた。気づかずにネームサーバーを切り替えると、Access も自前の WAF ルールもない状態で Proxy を通る（今回も一晩その状態でした。露出は切替前と同じ） | ネームサーバー切替前に DNS only（グレー雲）へ戻す |
| 2 | 「メールアドレスの難読化」が既定で ON。アプリの画面に表示するメールアドレスが書き換えられる | Scrape Shield の設定で OFF |
| 3 | Bot Fight モードは Free プランでは WAF ルールで例外を作れない。SNS の通知を止める恐れがある | OFF |
| 4 | WAF の Skip ルールで「残りのすべてのカスタムルール」と「このフェーズの残りのすべてのカスタムルール」を同時に選ぶと `'phase' and 'ruleset' cannot be used together` で保存できない | どちらか一方だけ選ぶ |
| 5 | Zero Trust の初期設定（チーム名の決定）は Cloudflare アカウントのスーパー管理者しかできない | お客様に初期設定と「Cloudflare Zero Trust」ロールの付与を依頼。チーム名は自動生成になるので後で変更 |

## 結果と、閉じたまま運用するときの注意

| 項目 | Before | After |
| --- | --- | --- |
| サーバーの受信ポート（Web） | 80 / 443 を全世界に公開 | **閉鎖**。経路は Tunnel のみ |
| サーバーの IP への直接アクセス | 可能 | 不可（タイムアウト） |
| ログイン | ID とパスワードのみ | メール認証コード（許可したアドレスだけ）＋ 従来の ID とパスワード |
| ブラウザ向けの証明書 | サーバー上の Let's Encrypt | Cloudflare の証明書（自動更新） |
| WAF、レート制限 | なし | あり（Free プランの範囲） |
| アプリのコード変更 | — | 0行 |
| 追加の月額費用 | — | 0円（Cloudflare Free ＋ Zero Trust Free） |
| 作業期間 | — | 事前調査 1日、設定と切替 2日 |

お客様から見た変化は、ログイン時に認証コードの入力画面が1枚増えたことだけです。メール送信、通知の受信、証明書の更新は切替前と同じように動いています。

切替直後の確認として、Access のログイン、検索・ページ送り・CSV 出力、通知元からの `POST` が 200 で届くこと、通知元以外からの `POST` が 403 になること、公開ページがログインなしで開けることを確かめました。翌日以降の定期バッチの動作と、Let's Encrypt の更新処理の整理は引き続き追っています。

### 閉じたまま運用するときの注意

80 / 443 を閉じた後は、サイトへの経路が cloudflared だけになります。そのぶん、運用で押さえておくことがあります。

- **cloudflared が止まるとサイトが開けなくなる。** systemd に登録して再起動時に自動で上がるようにし、開けないときは最初に cloudflared の状態と Cloudflare 側の Tunnel が Healthy かを見る
- **サーバー移行時は、新サーバーに cloudflared を入れて同じ Tunnel につなぎ直す。** 旧サーバーの cloudflared は止める。同じ Tunnel に2台つながると、リクエストが振り分けられてしまう
- **cloudflared 自体の更新を忘れない。** 今回は既存サーバーのパッケージ構成に手を入れたくなかったので、apt リポジトリを追加せず `.deb` を直接入れて `cloudflared service install` でサービス登録しています。この入れ方では自動更新されないので、定期的に入れ直す
- **サーバー側の証明書は内部用になる。** ブラウザ向けは Cloudflare の証明書で自動更新され、Let's Encrypt の証明書は cloudflared と Apache の間でしか使われない。期限が切れてもサイトは止まらない設定（No TLS Verify）にしているが、更新処理をどう整理するかは決めておく
- **接続元の IP はサーバーのログに残らない。** 必要なら Cloudflare 側のログを見るか、`CF-Connecting-IP` ヘッダを記録する設定を入れる

## まとめ

- **Cloudflare Tunnel を使えば、サーバーの 80 / 443 を閉じたまま Web を公開できる。** IP を知られてもつなぐ先がない。Access を置くなら、IP 直叩きでの素通りをなくすために Tunnel まで進める
- **攻撃はドメイン名だけでなく IP アドレスにも来る。** PHP2Shell のような脆弱性が出ても、80 / 443 が閉じていれば届かず、ドメイン経由は Access の手前で止まり、残りの経路は Cloudflare の管理ルールが受ける。それでも PHP 本体の更新は別途必要
- **既存システムは作り直さなくても、前段に Cloudflare Access を置ける。** 今回はアプリのコードを1行も変えずに、メール認証コードと Tunnel まで入れられました。ただし Access のセッションはアプリのログアウトと連動しないので、「毎回コード」を厳密にやるならアプリ側の改修が要ります
- **先に「人が使わない経路」を洗い出し、Bypass にはもう1枚 WAF を重ねる。** ポートを閉じて壊れるのは、人が使う画面ではなく webhook や証明書更新のほうです。認証を外したパスは誰でも叩けるので、通知元だけ通す Skip と、それ以外を止める Block を組にする
- **段階ごとに戻し方を決め、本番の前にテスト用ホスト名と本物の通知で確かめる。** 戻す順番（ファイアウォールを開けてから DNS を戻す）まで含めて決めておく

## よくある質問

**Q. 80 と 443 を閉じているのに、どうやってサイトが表示されるのですか？**

A. サーバー内の cloudflared というプロセスが、Cloudflare へ外向きに接続を張り続けています。利用者のブラウザからのリクエストは Cloudflare に届き、その外向きの接続の中を通ってサーバーへ渡されます。サーバーは外からの接続を待ち受けていないので、ファイアウォールで 80 / 443 を閉じたままで構いません。

**Q. Cloudflare の Proxy（オレンジ雲）を通すだけでは不十分ですか？**

A. Proxy だけだと、サーバーの IP アドレスが何らかの理由で分かったときに、直接つながれて Access を素通りされます。ファイアウォールで 80 / 443 を Cloudflare の IP 一覧だけに絞れば防げますが、IP 一覧が変わったときに追従する保守が残ります。Tunnel ならそもそも開いているポートがないので、IP 一覧の保守も不要です。

**Q. PHP2Shell のような PHP 本体の脆弱性が出たとき、この構成なら更新しなくても大丈夫ですか？**

A. 更新は必要です。この構成で変わるのは「更新するまでの猶予」です。IP アドレスをスキャンする攻撃はサーバーに届かず、ドメイン名経由の攻撃は Access の認証画面で止まり、認証を外した経路には Cloudflare の管理ルールが効きます。PHP2Shell では脆弱性公開の翌日に Free プラン向けのルールにも緊急リリースが出ました。それでも WAF は時間を稼ぐ層であって、根本の対策は PHP の修正版への更新です。

**Q. 無料プランでどこまでできますか？**

A. 今回の構成はすべて無料の範囲です。Tunnel は無料、Zero Trust は50ユーザーまで無料、WAF カスタムルールは5本、レート制限ルールは1本まで使えます。オリジンの応答待ち時間（既定125秒）を延ばせるのは Enterprise プランだけなので、重い処理がある場合は事前に計測してください。

---

## ご相談お待ちしております。

自社のサーバーの IP アドレスに、世界中から直接つなげる状態になっていないでしょうか。「プログラムの改修には時間がかかるが、入口だけでも先に固めたい」という状況は、多くの会社に共通しています。幸い、既存のシステムを止めずに、作り直さずに、ポートを閉じて前段の守りを後付けする方法があります。

**栃木県でシステム開発・アプリ開発・Web サイト制作の会社をお探しの方、既存の業務システムや管理画面への前段認証の導入、Cloudflare Tunnel / Access の設計と切替、レガシーシステムのセキュリティ強化をご検討中の方へ。** システムデザインワークスでは、今回のように現状の調査から切替計画、設定、検証まで一貫してサポートしております。「このサーバーに外からどの経路で届くのか」を一覧にするところからで構いません。お気軽にお問い合わせフォームよりご連絡ください。

---

参考（一次情報・2026年11月3日時点）:

- [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/)
- [Cloudflare Access: One-time PIN（コードの有効期限10分）](https://developers.cloudflare.com/cloudflare-one/integrations/identity-providers/one-time-pin/) / [Access のセッション管理（Global session とアプリのセッション）](https://developers.cloudflare.com/cloudflare-one/identity/users/session-management/)
- [Cloudflare Zero Trust 料金ページ](https://www.cloudflare.com/plans/zero-trust-services/)
- [WAF カスタムルール（プラン別の本数）](https://developers.cloudflare.com/waf/custom-rules/) / [レート制限ルール](https://developers.cloudflare.com/waf/rate-limiting-rules/)
- [Bot Fight Mode（WAF ルールで例外を作れないことの記載）](https://developers.cloudflare.com/bots/get-started/bot-fight-mode/)
- [Error 524（既定 125秒）](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-524/)
- [Amazon SNS のメッセージ配信の再試行（HTTP/S の既定ポリシー）](https://docs.aws.amazon.com/sns/latest/dg/sns-message-delivery-retries.html)
- PHP2Shell: [PHP の2件の深刻な脆弱性（CVE-2026-109618、CVE-2026-109619）についてまとめてみた（piyolog）](https://piyolog.hatenadiary.jp/entry/2026/10/21/174516) / [php/php-src のセキュリティアドバイザリ](https://github.com/php/php-src/security/advisories) / [CISA Known Exploited Vulnerabilities Catalog（2026-10-26 追加）](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)
- Cloudflare の対応: [WAF Release - 2026-10-22 - Emergency - Cloudflare Free Ruleset](https://developers.cloudflare.com/changelog/post/2026-10-22-emergency-waf-release-free/) / [WAF Release - 2026-10-28](https://developers.cloudflare.com/changelog/post/2026-10-28-waf-release/) / [WAF Release - 2026-10-30](https://developers.cloudflare.com/changelog/post/2026-10-30-waf-release/) / [Managed rules（Free プランで使えるルールセット）](https://developers.cloudflare.com/waf/managed-rules/) / [WAF for everyone（Free プランへの管理ルール提供の発表、2022年）](https://blog.cloudflare.com/waf-for-everyone/)
