キャッシュされたレスポンスは、すべて キャッシュキー の下に保存されます。リクエストが届くと、Cloudflare はそのリクエストのキャッシュキーを計算して検索します。ヒットすると保存済みのレスポンスを返します。ミスすると Worker が実行され、そのレスポンスがそのキーの下に保存され、次回以降に使われます。
同じキャッシュキーになる 2 つのリクエストは、同じキャッシュ済みレスポンスを共有します。キャッシュキーが異なる 2 つのリクエストは、それぞれ独立したキャッシュエントリになります。
このページでは、Workers Caching がキャッシュキーに何を入れるか、各要素がなぜ必要か、Worker を設計するときにどう考えればよいかを説明します。
Workers Caching は、次の要素でレスポンスをキー付けします。
- 対象のエントリポイント — リクエストを受け取った Worker の、特定の 名前付きエントリポイント です。
defaultエクスポートとエクスポートしたクラスは別のエントリポイントであり、同一のレスポンスを返してもキャッシュは共有しません。 - リクエスト URL の パスとクエリ文字列。クエリパラメータの順序は意味を持ちます。
?a=1&b=2と?b=2&a=1は別のキャッシュキーです。末尾のスラッシュも区別されます。 - デフォルトでは Worker のバージョン。デプロイした各バージョンは独自のキャッシュを持ちます。そのため、新しいデプロイが以前のバージョンが書いたレスポンスを返すことはありません。
cache.cross_version_cacheでオフにすると、バージョンをまたいでキャッシュ済みレスポンスを共有できます。デプロイをまたいだキャッシュの無効化 を参照してください。 - Worker がサービスバインディングまたは RPC 経由で呼び出されたときの、呼び出しの
ctx.props。ctx.propsによるマルチテナントの安全性 を参照してください。
キャッシュポイズニング対策として、キーには次も含みます。
- リクエストヘッダー
x-http-method-override、x-http-method、x-method-override。 - リクエストヘッダー
x-forwarded-host、x-host、x-forwarded-scheme(値がhttpまたはhttpsの場合を除く)、x-original-url、x-rewrite-url、forwarded。 - リクエストヘッダー
Cloudflare-Workers-Version-Keyの値。このヘッダーは Cloudflare が自動ではセットしません。呼び出し元(上流の Worker やプロキシなど)が、キャッシュをさらに明示的に分割するために付けた場合にだけ意味を持ちます。これは、上で述べた自動のバージョン単位キー付けとは独立しており、自動のキー付けはcache.cross_version_cacheで制御します。
この 3 点は、通常は意識する必要はありません。一部のフレームワークは、method-override や URL-rewrite のヘッダーを、リクエストの実効メソッドや URL を上書きするものとして解釈します。ヘッダーだけが違い、レスポンスの中身が大きく異なる 2 つのリクエストがあると、キャッシュポイズニング ↗ につながることがあります。これらをキャッシュキーに含めると、汚染されたエントリは、同じ汚染ヘッダーを持つリクエストにしか影響しません。
キャッシュキーに含まれないリクエストヘッダーだけが違うリクエスト(たとえば User-Agent、Accept-Language、Cookie、Authorization)は、同じキャッシュ済みレスポンスを返します。これが通常の望ましい動作です。ユーザーエージェント文字列や言語設定ごとに別エントリを作りたくないからです。コンテンツネゴシエーションが必要な場合は、レスポンスに Vary をセットするか、Worker 内で処理して URL ごとに正規化したレスポンスを返してください。
特に、キャッシュキーには次を 含みません。
- HTTP メソッド。 同じ URL の
GETとHEADは、1 つのキャッシュエントリを共有します。HEADリクエストは、GETで埋めたエントリから返せます(Cloudflare はキャッシュ済みヘッダーを本文なしで返します)。逆方向では、コールドキャッシュへのHEADは内部でGETに変換され、アセット全体を取得して保存します。その後のGETは、HEADが埋めたエントリにヒットします。(POST、PUT、PATCH、DELETEは一切キャッシュされないため、この問題は起きません。) - リクエストのホスト。 Worker のキャッシュはフル URL ではなく、パスとクエリ文字列でキー付けします。キャッシュはドメインではなく Worker に属する を参照してください。
- リクエストボディ。 キャッシュ対象は
GETとHEADだけなので、通常は関係ありません。ただし、キャッシュ可能なメソッドで Worker がrequest.bodyを読む場合でも、ボディはキャッシュを分割しません。
リリース時点では、Cloudflare がリクエストに対して計算した正確なキャッシュキーは確認できません。キャッシュの挙動を把握するための主な手がかりは、レスポンスヘッダー Cf-Cache-Status と、Workers の可観測性ダッシュボード の呼び出し単位のキャッシュヒット情報です。キャッシュキーの確認 を参照してください。
Worker はゾーンに紐づかないエンティティです。次のような複数の経路から呼び出せます。
workers.devサブドメインへの直接アクセス。- 管理下の任意のゾーン上の ルート。
- カスタムドメイン — 同じ Worker を複数のカスタムドメインにバインドできます。
- 別の Worker からの サービスバインディング。URL には任意のプレースホルダーホスト名を使えます。
Workers Caching は、これらをすべて同じ Worker として扱い、1 つの共有キャッシュを使います。キャッシュキーにホストは含まれません。そのため、/api/users/42 へのリクエストは、api.example.com、api.example.net、サービスバインディング、workers.dev URL のどれから来ても、同じキャッシュエントリにヒットします。
これが、ほとんどの場合に望ましい動作です。Worker のレスポンスは、どのドメインから届いたかではなく、コードと入力の関数です。一度キャッシュして、すべての入口に同じレスポンスを返すと、正しさを損なわずにヒット率が最大化します。
同じパスでもホスト名ごとに違うキャッシュ済みレスポンスが本当に必要な場合 — たとえばホワイトラベルのテナントで、tenant-a.example.com/index と tenant-b.example.com/index の内容を分ける必要がある場合 — キャッシュキーは自動ではこれを行いません。代わりに、ゲートウェイ Worker でテナントを区別し、テナント識別子を ctx.props 経由で渡してください。ctx.props はキャッシュキーの一部です。
デフォルトでは、現在呼び出されている Worker のバージョン は キャッシュキーの一部です。デプロイした各バージョンは独自のキャッシュを持つため、次のようになります。
- 新しいデプロイはコールドキャッシュから始まり、以前のバージョンが書いたレスポンスを返すことはありません。
- キャッシュに影響する変更は、新しいバージョンが稼働した時点で直ちに適用されます。古いコンテンツの配信を止めるためにパージする必要はありません。
- 段階的デプロイ 中、旧バージョンと新バージョンは独立したキャッシュを埋めます。そのため、新バージョン側のトラフィックが旧バージョンのレスポンスを受け取ることはありません。
これがデフォルトなのは、最も考えやすい動作だからです。トレードオフは、デプロイのたびにキャッシュヒット率がリセットされる ことです。新バージョンへの最初のリクエストはミスになり、キャッシュが埋まるまで続きます。デプロイ直後に Worker のキャッシュヒット率が下がる、最も多い理由です。
デプロイ頻度が高く、デプロイ間でレスポンスがほとんど変わらない場合、ウォームなキャッシュを毎回捨てるのは無駄です。cache.cross_version_cache を true にすると、キャッシュキーからバージョンが外れ、バージョンをまたいでキャッシュ済みレスポンスを共有します。バージョン A が書いたレスポンスは、TTL が切れていなければ、バージョン B のデプロイ後も配信されます。
ヒット率は最大化しますが、ロールアウトは遅くなります。デプロイがキャッシュを無効化しなくなるため、レスポンス内容を変える変更は、既存のキャッシュエントリが期限切れになるか、パージするまで反映されません。cross_version_cache を有効にしていて、デプロイを直ちに反映したい場合は、次の 2 つの手段のいずれかを使います。
細かく制御したい場合は、キャッシュした各レスポンスに、それを生成した Worker バージョンのタグを付けます。あとからそのバージョンタグをパージすると、そのバージョンが書いたエントリだけが削除され、他バージョンのキャッシュ済みレスポンスには影響しません。
これは バージョンメタデータバインディング で、リクエスト時に現在のバージョン ID を読み、Cache-Tag 値として先頭に付けます。コード付きの完全なパターンは バージョン単位のパージ を参照してください。
cross_version_cache を有効にしていて、正常なバージョンのキャッシュを消さずに特定バージョンだけロールバックする可能性がある場合に、最適な選択肢です。
より単純な方法です。各デプロイ後に、CI から小さな Worker エンドポイントを叩き、ctx.cache.purge({ purgeEverything: true }) を呼び出します。パージ後の次のリクエストは、その時点で稼働している Worker バージョンからキャッシュを再構築します。
粗い方法ですが、Worker 内のロジックは不要です。cross_version_cache を有効にしつつ、特定のデプロイではキャッシュを無効化したい場合に使います。デフォルトのバージョン単位キャッシュでは、デプロイはもともとコールドキャッシュから始まるため、この手順は不要です。
Worker が サービスバインディング または RPC 経由で呼び出されると、呼び出し元の ctx.props はキャッシュキーの一部になります。異なる ctx.props で Worker を呼び出す 2 つの呼び出し元は、別々のキャッシュエントリ を得ます。ある呼び出し元が、別の呼び出し元のキャッシュ済みレスポンスを受け取ることはありません。
これが、サービスバインディング経由で呼び出すマルチテナント Worker で、キャッシュを安全にする仕組みです。呼び出し元ごとの認可コンテキスト — ユーザー ID、テナント ID、組織、ロール — を ctx.props で運ぶ場合、キャッシュはデフォルトで安全です。ある呼び出し元に属するレスポンスが、キャッシュ経由で別の呼び出し元に漏れることはありません。
import { WorkerEntrypoint } from "cloudflare:workers";
export default class Backend extends WorkerEntrypoint {
async fetch(request) {
// ctx.props.userId is set by the caller (for example, an auth gateway).
// Because it is part of the cache key, User A and User B requesting the
// same URL get separate cache entries — there is no way for one to
// see the other's response.
const { userId } = this.ctx.props;
const data = { userId, timestamp: Date.now() };
return new Response(JSON.stringify(data), {
headers: {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=300",
},
});
}
}import { WorkerEntrypoint } from "cloudflare:workers";
interface Props {
userId: string;
}
export default class Backend extends WorkerEntrypoint<Env, Props> {
async fetch(request: Request): Promise<Response> {
// ctx.props.userId is set by the caller (for example, an auth gateway).
// Because it is part of the cache key, User A and User B requesting the
// same URL get separate cache entries — there is no way for one to
// see the other's response.
const { userId } = this.ctx.props;
const data = { userId, timestamp: Date.now() };
return new Response(JSON.stringify(data), {
headers: {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=300",
},
});
}
}サービスバインディングの呼び出しでは、渡す URL の意味が直感と違うため、特に注意が必要です。
サービスバインディングを fetch() で呼ぶとき、URL のホスト名はプレースホルダーです。リクエストは DNS ではなくバインディング経由でルーティングされ、ホスト名は解決されません。ホストはキャッシュキーの一部でもないため(キャッシュはドメインではなく Worker に属する で説明したとおり)、プレースホルダーはキャッシュにも影響しません。キャッシュキーに寄与するのは パス(とクエリ文字列)だけで、対象エントリポイントと ctx.props がそれに加わります。
export default {
async fetch(request, env, ctx) {
// "internal" here is just a placeholder — it is not routed anywhere
// and is not part of the cache key.
//
// What identifies this cached response is:
// - the BACKEND entrypoint
// - the path "/api/users/42"
// - whatever ctx.props the gateway passes along
return env.BACKEND.fetch("http://internal/api/users/42");
},
};interface Env {
BACKEND: Fetcher;
}
export default {
async fetch(request, env, ctx): Promise<Response> {
// "internal" here is just a placeholder — it is not routed anywhere
// and is not part of the cache key.
//
// What identifies this cached response is:
// - the BACKEND entrypoint
// - the path "/api/users/42"
// - whatever ctx.props the gateway passes along
return env.BACKEND.fetch("http://internal/api/users/42");
},
} satisfies ExportedHandler<Env>;呼び出し元ごとにキャッシュ済みレスポンスを分けたい場合は、ctx.props を変えます。リクエストごとに分けたい場合は、パスまたはクエリ文字列を変えます。ホスト名を変えても効果はありません。
リリース時点では、キャッシュの挙動を知る手がかりは次の 2 つです。
-
レスポンスヘッダー
Cf-Cache-Status。 よく見る値はHIT、MISS、EXPIRED、REVALIDATED、UPDATING、STALE、BYPASSです。HITは、Worker を実行せずにキャッシュ済みレスポンスを返したことを意味します。MISSは Worker が実行され、レスポンスが保存されたことを意味します。UPDATINGはキャッシュ済みレスポンスが古く、バックグラウンドで Worker が実行されて更新されたことを意味します。BYPASSは、このリクエストではキャッシュが無効だったことを意味します。値の全体は Cloudflare のキャッシュレスポンス を参照してください。 -
Workers の可観測性ダッシュボード のキャッシュヒット。 各呼び出しで、キャッシュから配信されたかどうかが分かるので、Worker のトラフィック全体でキャッシュヒットの挙動をフィルタしたり集計したりできます。
Cloudflare は現在、キャッシュキーの構成そのものは公開していません。同じキャッシュ済みレスポンスを共有するはずの 2 つのリクエストが共有しない場合は、キャッシュキーに含まれるもの に挙げた要素のどれが違ったかを考える必要があります。よくあるキャッシュの問題と切り分け方は デバッグ を参照してください。
デフォルトでは、リクエスト URL のパスとクエリ文字列が、キャッシュキーの URL 成分になります。あるエントリポイントが ctx.exports のループバック経由で、キャッシュ対象の別エントリポイントを呼ぶとき、呼び出し側はリクエストに cf.cacheKey をセットして、その成分を上書きできます。
次の例では、Backend エントリポイントがキャッシュ対象です。デフォルトのエントリポイントは ctx.exports 経由でリクエストを転送し、キャッシュキーを自分で決めます。
import { WorkerEntrypoint } from "cloudflare:workers";
// Cached entrypoint. Requests routed here through ctx.exports are served
// from cache when possible.
export class Backend extends WorkerEntrypoint {
async fetch(request) {
return new Response("Hello from the backend", {
headers: {
"Content-Type": "text/html",
"Cache-Control": "public, max-age=3600",
},
});
}
}
// Gateway entrypoint. Calls the cached Backend entrypoint via ctx.exports,
// which routes through the cache, and chooses the cache key for the call.
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// Strip a tracking parameter so that requests differing only by
// `utm_source` resolve to the same cached entry.
url.searchParams.delete("utm_source");
return ctx.exports.Backend.fetch(request, {
cf: { cacheKey: url.pathname + url.search },
});
},
};import { WorkerEntrypoint } from "cloudflare:workers";
// Cached entrypoint. Requests routed here through ctx.exports are served
// from cache when possible.
export class Backend extends WorkerEntrypoint<Env> {
async fetch(request: Request): Promise<Response> {
return new Response("Hello from the backend", {
headers: {
"Content-Type": "text/html",
"Cache-Control": "public, max-age=3600",
},
});
}
}
// Gateway entrypoint. Calls the cached Backend entrypoint via ctx.exports,
// which routes through the cache, and chooses the cache key for the call.
export default {
async fetch(request, env, ctx): Promise<Response> {
const url = new URL(request.url);
// Strip a tracking parameter so that requests differing only by
// `utm_source` resolve to the same cached entry.
url.searchParams.delete("utm_source");
return ctx.exports.Backend.fetch(request, {
cf: { cacheKey: url.pathname + url.search },
});
},
} satisfies ExportedHandler<Env>;カスタムキャッシュキーは、キャッシュキー内の パスとクエリ文字列を置き換えます。キャッシュキーに含まれるもの で説明した他の要素は、そのまま適用されます。
- 対象のエントリポイントと、呼び出し元の
ctx.propsは、引き続きキーの一部です。カスタムキャッシュキーはエントリポイントをまたいだり、ctx.propsをまたいだりできません。そのため、呼び出し元が自分でキーを選んでも、上で述べた マルチテナントの分離 は維持されます。カスタムキーが指せるのは、呼び出し先自身のキャッシュ名前空間内のエントリだけです。 - URL は違うが
cf.cacheKeyが同じ 2 つのリクエストは、同じキャッシュエントリに解決されます。複数 URL を 1 つのキャッシュ済みレスポンスにまとめる方法です。 - URL は同じだが
cf.cacheKeyが違う 2 つのリクエストは、別々のキャッシュエントリに解決されます。
cf.cacheKey を空文字列にするか省略すると、デフォルトの URL 由来キーに戻ります。
このパターンでは、デフォルトのエントリポイントはすべてのリクエストで動くゲートウェイなので、こちらではキャッシュを無効にし、Backend では有効のままにします(エントリポイント単位のキャッシュ を参照)。
{
"name": "my-worker",
"main": "src/index.ts",
// Set this to today's date
"compatibility_date": "2026-09-20",
"cache": { "enabled": true },
"exports": {
"default": { "type": "worker", "cache": { "enabled": false } },
"Backend": { "type": "worker", "cache": { "enabled": true } },
},
}name = "my-worker"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-09-20"
[cache]
enabled = true
[exports.default]
type = "worker"
[exports.default.cache]
enabled = false
[exports.Backend]
type = "worker"
[exports.Backend.cache]
enabled = true- URL の一部を無視する。 トラッキングパラメータ(
utm_source、gclid)を除く、またはクエリ文字列をすべて落とすと、レスポンスを変えないばらつきが 1 つのキャッシュエントリを共有します。 - URL 以外でキー付けする。 ゲートウェイ Worker が信頼する値 — たとえば正規化したリソース識別子 — からキーを組み立て、同等の複数 URL を 1 エントリに対応付けます。
- 自分でキャッシュを分割する。 キーに識別用の値(コンテンツバージョンなど)を付けて、本来ぶつかるリクエストを別エントリにします。
呼び出し元ごとの分離には、呼び出し元の識別をキャッシュキーに埋め込むのではなく、引き続き ctx.props を使ってください。ctx.props は自動でキーの一部になり、迂回できません。
cf.cacheKey が有効になるのは、呼び出しが自分のアカウント内に留まる場合だけです。リクエストがアカウント境界をまたぐと — たとえば別アカウントが所有する Worker へのサービスバインディング — Cloudflare は cf オブジェクトを落とします。その場合、カスタムキーは無視され、キャッシュキーはリクエスト URL に戻ります。あるアカウントの呼び出し元が、別アカウントの Worker のキャッシュに影響したり探ったりすることはできません。
そのため、cf.cacheKey はアイボール(エンドユーザー)リクエストにも効果がありません。ブラウザや API クライアントからの受信リクエストの cf オブジェクトは、クライアントではなく Cloudflare が埋めるため、クライアントが自分でキャッシュキーをセットすることはできません。