← 記事一覧に戻る
Application Services2026年10月22日読了 27分19 views

Cloudflare Cache Rules の活用と評価順について

1. Cloudflare Cache Rulesについて

Page Rules は1ゾーンあたりの本数制限が厳しく、1ルールに複数アクションを詰め込む設計で、評価モデルも「最初にマッチした1本で終端」という単純なもの。Cloudflare は後継として、機能別に分かれた Rules 製品群(Cache Rules / Configuration Rules / Origin Rules / Transform Rules …)を提供しており、キャッシュ制御は Cache Rules が担います。

Cache Rules の利点:

  • 式(expression)ベースのマッチ: http.request.uri.path、http.host、http.cookie などを Wirefilter 構文で柔軟に記述できる。
  • 設定が細分化: 適格性・Edge TTL・Browser TTL・Cache Key・Cache Reserve などを個別に指定。
  • Page Rules より優先: 同一パスで両方が該当した場合、設計上 Cache Rules が優先される。

公式: Cache Rules / Create a cache rule via API


2. Eligible for cache / Edge TTL / Browser TTLについて

2-1. Eligible for cache(適格性)

  • キャッシュ対象にする(= Eligible for cache)
  • キャッシュ対象にしない (= Bypass cache)

2-2. Edge TTL(エッジでの保持時間)

edge_ttl.mode は3択:

mode 意味
respect_origin origin の Cache-Control に従う。無ければ Cloudflare デフォルト
override_origin origin の Cache-Control を無視し、default(秒)で固定
bypass_by_default デフォルト TTL を使わない
"edge_ttl": { "mode": "override_origin", "default": 86400 }

ステータスコード別 TTL(status_code_ttl)も指定可能。value: 0 は no-cache、value: -1 は no-store。

2-3. Browser TTL(ブラウザ向け max-age)

browser_ttl.mode は respect_origin / override_origin / bypass_by_default。override_origin ならクライアントに返す Cache-Control: max-age を書き換える。

"browser_ttl": { "mode": "override_origin", "default": 3600 }

実測ハマりどころ: Browser TTL の default に取れる値はプランごとに決まった選択肢がある。検証で 60(秒)を指定したら無視され origin 値のままになったが、3600 は正しく反映された。小さすぎる値は弾かれる/丸められると考えてよい。公式: Browser Cache TTL

公式の設定一覧: Cache Rules settings

2-4. Edge TTL と Browser TTL は別レイヤー

Edge TTL は Cloudflare のエッジが保持する時間、Browser TTL はクライアント(ブラウザ)に保持させる時間。指す場所が違う。

sequenceDiagram
    participant B as Browser
    participant E as Cloudflare Edge
    participant O as Origin
    Note over B: Browser TTL<br/>(Cache-Control: max-age)
    Note over E: Edge TTL<br/>(エッジ保持時間)
    B->>E: GET /assets/main.css
    E->>O: MISS → origin へ取得
    O-->>E: 200 (Cache-Control: max-age=14400)
    Note over E: edge_ttl override=86400 で保持<br/>browser_ttl override=3600 に書換
    E-->>B: 200 (Cache-Control: max-age=3600)
    B->>E: 再リクエスト
    E-->>B: HIT(エッジから即応答)

3. 評価順について

公式 Order and priority より:

Cache Rules are stackable. ... If several matching rules set a value for the same setting, the value in the last matching rule wins.
For conflicting settings (bypass cache vs eligible for cache), the last matching rule wins.

つまり:

Page Rules Cache Rules
マッチ 最初の1本で終端 全マッチを合成(stackable)
競合時 最初のルールが有効 最後にマッチしたルールが勝つ

Page Rules の感覚で「具体ルールを上、catch-all を下」に並べると事故る。 Cache Rules では catch-all(広いルール)を上、具体ルールを下に置く必要がある。

評価モデルを並べるとこうなる。

flowchart TB
    subgraph PR["Page Rules: 最初にマッチで終端"]
        direction TB
        p1{"rule #1 マッチ?"} -->|Yes| pa["このルールを適用して終了"]
        p1 -->|No| p2{"rule #2 マッチ?"}
        p2 -->|Yes| pb["このルールを適用して終了"]
        p2 -->|No| p3["..."]
    end
    subgraph CR["Cache Rules: 全マッチを合成・後勝ち"]
        direction TB
        c1["rule #1 マッチ → 設定を適用"] --> c2["rule #2 マッチ → 上書き"]
        c2 --> c3["rule #3 マッチ → さらに上書き"]
        c3 --> cw["最後にマッチしたルールの値が勝つ"]
    end

3-1. アンチパターンを実機で再現

taka-cloudflare.win で、わざと Page Rules 的な順序(catch-all を最後)で投入したときの検証ログ:

== /api/products ==   ← cache:false を意図したのに…
  #1 cf-cache-status: MISS
  #2 cf-cache-status: HIT      ← キャッシュされてしまう

== /assets/app.css ==  ← Edge 1日 / Browser 1時間を意図したのに…
  cache-control: public, max-age=1800   ← origin の値のまま(override 無効)

原因は、末尾の catch-all (http.host eq "taka-cloudflare.win") が /api にも /assets にもマッチし、後勝ちで cache:true / respect_origin に上書きしていたから。公式ドキュメントの例(rule#1 で images をキャッシュ、rule#2 で全体を bypass → 全部 bypass)と全く同じ構図だ。

/api/products への1リクエストが、順序によって正反対の結末になる様子:

flowchart LR
    req(["GET /api/products"])
    subgraph WRONG["誤: 具体→一般(catch-all が最後)"]
        direction TB
        w1["/api → cache:false"] --> w2["host catch-all → cache:true"]
        w2 --> wres["❌ 後勝ちで cache:true<br/>→ HIT してしまう"]
    end
    subgraph RIGHT["正: 一般→具体(catch-all が最初)"]
        direction TB
        r1["host catch-all → cache:true"] --> r2["/api → cache:false"]
        r2 --> rres["✅ 後勝ちで cache:false<br/>→ DYNAMIC(bypass)"]
    end
    req --> WRONG
    req --> RIGHT

3-2. 正しい順序(一般 → 具体)

apply.sh が投入する最終形(上から評価、後勝ち):

1) 一般 : (http.host eq HOST)                         cache:true, edge=override 5m, browser=override 30m
2) 具体 : host and starts_with(path,"/assets/")        cache:true, edge=override 1d, browser=override 1h
3) 具体 : host and starts_with(path,"/api/")           cache:false

4. ハンズオン(API スクリプト)

リポジトリの scripts/cache-rules/ に再現一式を置いた。

scripts/cache-rules/
├── lib.sh                 # 共通(API 呼び出し / .env 読込)
├── .env.example           # CF_API_TOKEN / CF_ZONE_ID / TEST_HOST
├── apply.sh               # 正しい順序でルールを投入(entrypoint を PUT)
├── apply-wrong-order.sh   # ★アンチパターン★ catch-all を最後に置いた版
├── list.sh                # 現在の Cache Rules を表示
├── verify.sh              # 各パスを2回叩いて cf-cache-status を観察
└── cleanup.sh             # 全 Cache Rules を削除(entrypoint を空に)

4-1. 必要なトークン権限

公式 Required API token permissions より、Cache Rules を API で管理するには:

  • Zone → Cache Rules → Edit
  • Account → Account Rulesets → Edit
  • Account → Account Filter Lists → Edit

加えて Zone Resources に対象ゾーンを Include すること(ここが抜けると Authentication error (code 10000) になる)。

4-2. セットアップ

cp scripts/cache-rules/.env.example scripts/cache-rules/.env
# .env に CF_API_TOKEN / CF_ZONE_ID / TEST_HOST を設定

4-3. ルール投入の本体(entrypoint を PUT)

Cache Rules は http_request_cache_settings フェーズの entrypoint ruleset に入る。apply.sh の中核はこれだけ:

curl -sS --request PUT \
  "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/rulesets/phases/http_request_cache_settings/entrypoint" \
  --header "Authorization: Bearer ${CF_API_TOKEN}" \
  --header "Content-Type: application/json" \
  --data '{
    "rules": [
      { "description": "1) general",
        "expression": "(http.host eq \"cache-test.aspa-checker.win\")",
        "action": "set_cache_settings",
        "action_parameters": { "cache": true,
          "edge_ttl":   { "mode": "override_origin", "default": 300 },
          "browser_ttl":{ "mode": "override_origin", "default": 1800 } } },
      { "description": "2) /assets strong cache",
        "expression": "(http.host eq \"cache-test.aspa-checker.win\" and starts_with(http.request.uri.path, \"/assets/\"))",
        "action": "set_cache_settings",
        "action_parameters": { "cache": true,
          "edge_ttl":   { "mode": "override_origin", "default": 86400 },
          "browser_ttl":{ "mode": "override_origin", "default": 3600 } } },
      { "description": "3) /api bypass",
        "expression": "(http.host eq \"cache-test.aspa-checker.win\" and starts_with(http.request.uri.path, \"/api/\"))",
        "action": "set_cache_settings",
        "action_parameters": { "cache": false } }
    ]
  }'

注意: PUT .../entrypoint はフェーズ内の Cache Rules を丸ごと置き換える。既存ルールがあるなら、先に list.sh で吸い出してマージすること。Page Rules には影響しない。


5. 検証結果(実機・クリーン環境)

検証は cache-test.aspa-checker.win(Snippet/Worker が介在しない、素のオリジンが Cloudflare 配下のホスト)で実施。

5-1. Before(Cache Rules なし)

/                status=200  cf-cache-status=DYNAMIC  cache-control=public, max-age=86400
/assets/x.css    status=404  cf-cache-status=MISS     cache-control=public, max-age=14400
/api/products    status=404  cf-cache-status=DYNAMIC  cache-control=(なし)

HTML(/)はデフォルトでキャッシュされず DYNAMIC。

5-2. After(正しい順序の Cache Rules)

--- / (一般ルール: cache:true, edge override) ---
  #1 cf-cache-status=MISS
  #2 cf-cache-status=HIT          ← DYNAMIC だった HTML がキャッシュされた

--- /assets/main.css (具体ルール: Browser TTL override 1h) ---
  #1 cf-cache-status=MISS  | cache-control: public, max-age=3600   ← origin 14400 → 3600 に書換
  #2 cf-cache-status=HIT   | cache-control: public, max-age=3600

--- /api/items (具体ルール: cache:false) ---
  #1 cf-cache-status=DYNAMIC      ← bypass が効いている
  #2 cf-cache-status=DYNAMIC

確認できたこと:

  • 適格性: / が DYNAMIC → MISS → HIT。cache:true でキャッシュ対象化できた。
  • Browser TTL override: /assets/* の Cache-Control が origin の 14400 から 3600 に書き換わった。しかもこの値は一般ルール(1800)ではなく具体ルール(3600)が勝っている = 「後勝ち(last-match-wins)」の正しい挙動。
  • Bypass: /api/* は DYNAMIC のまま。cache:false が尊重されている。

5-3. cf-cache-status の読み方

値 意味
HIT エッジキャッシュから応答
MISS キャッシュに無く origin へ取りに行った(が適格)
EXPIRED / REVALIDATED TTL 切れ後の再取得 / 再検証
DYNAMIC キャッシュ対象外(適格でない)
BYPASS 設定により明示的に回避

6. もう一つの落とし穴: Snippets / Workers が Cache Rules を上書きする

最初、別ホスト(taka-cloudflare.win)で検証したところ、cache:false にしても全パスが HIT になり、bypass everything ルールにしても root が HIT のままだった。

調査の結果:

  • レスポンスに x-snippets-hello: ... ヘッダ → Snippet が稼働。
  • 実行順は公式の Execution order で ... → Cache Rules → Snippets → ...。
  • Snippet / Worker が Cache API(caches.default)で独自にキャッシュすると、その応答ヘッダが後勝ちし、Cache Rules の適格性も TTL も実質無効化される。
flowchart LR
    req([Request]) --> tr[Transform/Config/Origin Rules]
    tr --> cr["Cache Rules<br/>(cache:false を指定)"]
    cr --> sn["Snippets / Workers<br/>caches.default で独自キャッシュ"]
    sn --> resp(["Response<br/>cf-cache-status は Snippet の都合で HIT"])
    cr -. "本来は bypass のはず" .-> resp
    style cr fill:#e3f2fd
    style sn fill:#ffe0b2

教訓: Cache Rules がエッジで効くのは「Cloudflare のキャッシュ層が応答経路に入っている」場合だけ。前段に Worker/Snippet/Pages が独自キャッシュを挟むと、cf-cache-status はそちらの都合で出る。Cache Rules が「効かない」と思ったら、まず Worker/Snippet/Pages の有無を疑え。


7. 運用 Tips / チェックリスト

  • 順序: 広いルールを上、具体ルールを下(後勝ち)。Page Rules の逆だと覚える。
  • entrypoint PUT は全置換: 既存を吸い出してマージ。差分管理したいなら Terraform(cloudflare_ruleset)へ。
  • 適用後も古いオブジェクトは残る: ルール変更は「以後の評価」に効くだけ。既にキャッシュ済みの実体は期限切れかパージまで残る。検証時はクエリ文字列でキャッシュキーを変えるか、パージする。
  • Browser TTL の値はプラン依存: 小さすぎる値は無視される。
  • トークン権限: Zone:Cache Rules:Edit + Account:Account Rulesets:Edit + Account:Account Filter Lists:Edit、かつ対象ゾーンを Include。
  • 前段の Worker/Snippet/Pages を確認: Cache Rules の後段で動き、挙動を上書きし得る。

免責事項

本記事は Cloudflare Japan のソリューションエンジニアが技術的な知見の共有を目的として個人の立場で執筆したものです。記事の内容は執筆時点の情報および執筆者の主観的な見解を含んでおり、Cloudflare, Inc. の公式な見解・推奨・仕様保証を構成するものではありません。また、記載された構成や手順がすべてのお客様環境で同様に適用できることを保証するものでもありません。製品の正式な仕様や技術サポートについては、Cloudflare 公式ドキュメントまたは担当チームにお問い合わせください。