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["最後にマッチしたルールの値が勝つ"]
end3-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 --> RIGHT3-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→EditAccount→Account Rulesets→EditAccount→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 の後段で動き、挙動を上書きし得る。