CloudflareのR2の使い方|料金で失敗しない手順

CloudflareのR2の使い方|料金で失敗しない手順

こんにちは。Osukeラーニング、運営者の「osuke」です。

Cloudflare WorkersでAPIを作ったあと、「画像や動画はどこへ保存するのか」「R2へ置いたファイルをどう公開するのか」「egress無料なら料金は0円なのか」と迷っていませんか。R2は大きなファイルを扱いやすい一方、バケットを作っただけでは公開されず、保存容量と操作回数は別々に課金されます。

結論からいうと、最初はStandardのバケットを作り、Workers BindingでPUT・GET・DELETEを確認し、公開ファイルはカスタムドメイン、利用者ごとのアップロードは短時間の署名付きURLを選ぶと整理できます。料金は「GB-month+Class A+Class B+必要なら取得料」の4項目に分ければ、egress無料という一言に振り回されません。

この記事はosuke本人のR2利用体験としては書かず、2026年8月27日時点のCloudflare公式資料と、Xで公開された8件の保存・配信・CMS・アップロード・バックアップ・費用事例を照合しました。投稿者の結果はそれぞれの環境に限られ、R2全体の保証ではありません。Workersが初めてならCloudflare Workersの始め方、ファイルと表データの違いから整理するならCloudflare D1の使い方も先に確認してください。

記事のポイント

  • Standardバケットを作り、Workers Bindingで最小の読み書きを通す
  • 公開ファイル・非公開ファイル・直接uploadで入口を分ける
  • 容量だけでなくClass A・Class B・取得料まで見積もる
  • 本番前にCORS・Cache・予算通知・誤削除対策を確認する

Cloudflare R2の使い方を準備から進める

R2の接続は「保存先を作る」「Workerへ結び付ける」「1ファイルを往復させる」「公開方法を選ぶ」の4段階です。ここでは画像を1枚保存する小さなAPIを例にし、各段階で何ができれば成功かまで示します。

R2は画像やファイルを保存するオブジェクトストレージ

Cloudflare公式のR2概要によると、R2は画像、動画、PDF、ログ、配布物などの非構造化データを、オブジェクトという単位で保存するS3互換ストレージです。フォルダのように見える文字列も実体はkeyのprefixであり、データは「bucket名+object key」で識別します。

例えばユーザー画像なら、R2にはusers/123/avatar.webpというkeyで画像本体を置き、D1にはuser_id、object_key、mime_type、公開状態を行として保存します。画像そのものをSQLの列へ詰めるより、検索する情報と大きなバイナリを分ける構成です。Cloudflare製品の役割を広く比べたい場合は初心者向けCloudflare全体像も役立ちます。

Xでも、Aniket Patidar氏はPayload CMSの保存先へR2、データベースへD1を設定し、Trorram氏はバックアップツールの保存先候補としてR2を挙げています。前者はファイルと構造化データを分ける例、後者はS3互換の保存先として扱う例です。

反対に、商品価格、投稿日時、会員状態のように条件検索や並び替えを繰り返す値は、R2だけで管理すると一覧を作りにくくなります。R2にはfile本体、D1には検索用metadataという境界を先に決め、object keyを両者の共通IDにすると、後から保存先や表示方法を変えるときも追跡できます。

R2へ置きやすいデータ
  • Webサイトの画像・動画・音声・PDF
  • アプリから配布するインストーラーやZIP
  • ユーザーがアップロードするファイル
  • ログ、バックアップ、AI学習用の大きなデータ

R2バケットを作成してWorkersへBindingする

公式Workers APIの手順では、Wranglerへログインし、バケットを作成してからWorkerの設定へR2 Bindingを追加します。初回は無料枠が適用されるStandardを選び、バケット名はmy-app-assetsのように用途が分かるASCII名にします。

npx wrangler login
npx wrangler r2 bucket create my-app-assets
npx wrangler r2 bucket list

次にwrangler.jsoncへBindingを追加します。bucket_nameはCloudflare上の実体名、bindingはWorkerコードから呼ぶ変数名です。本記事ではASSETSに統一するため、コード側はenv.ASSETSとなります。

{
  "r2_buckets": [
    {
      "binding": "ASSETS",
      "bucket_name": "my-app-assets"
    }
  ]
}

管理画面からWorkerへBindingを追加する方法もありますが、次回のdeployで再現できるよう設定ファイルにも残します。TypeScriptならnpx wrangler typesを実行し、生成された型にASSETS: R2Bucketが含まれることを確認します。Binding名の大文字小文字が違うと、バケットが存在してもコードからは未定義です。

Binding完了の判定
  • bucket listにmy-app-assetsが出る
  • 設定にbinding: ASSETSがある
  • コードからはenv.ASSETSと呼ぶ
  • 開発用と本番用は別bucket名にする

Workerからput・get・deleteを確認する

Bindingができたら、最初から認証や画像変換を足さず、1つのkeyへ保存・取得・削除するAPIを作ります。PUT /files/hello.txtはrequest bodyを保存し、GETは本文とHTTP metadataを返し、DELETEは同じkeyを削除します。

interface Env { ASSETS: R2Bucket }

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const key = url.pathname.replace(/^\/files\//, "");

    if (!key) return new Response("key required", { status: 400 });

    if (request.method === "PUT") {
      await env.ASSETS.put(key, request.body, {
        httpMetadata: { contentType: request.headers.get("content-type") || "application/octet-stream" }
      });
      return new Response(null, { status: 201 });
    }

    if (request.method === "GET") {
      const object = await env.ASSETS.get(key);
      if (!object) return new Response("not found", { status: 404 });
      const headers = new Headers();
      object.writeHttpMetadata(headers);
      headers.set("etag", object.httpEtag);
      return new Response(object.body, { headers });
    }

    if (request.method === "DELETE") {
      await env.ASSETS.delete(key);
      return new Response(null, { status: 204 });
    }

    return new Response("method not allowed", { status: 405 });
  }
};

npx wrangler devで起動し、テキストをPUTして同じ内容をGETし、DELETE後のGETが404になるところまで確認します。R2への直接操作は公式の整合性資料で強整合とされ、書き込みや削除は完了後すぐ反映されます。ただし後述するCache経由の表示は別です。

この例は接続確認用で、誰でも書き込み・削除できるまま公開してはいけません。本番ではログイン済み利用者を確認し、keyの先頭へuser IDを付け、拡張子だけでなくMIME type、容量、ファイル内容を検査します。同じkeyへ上書きさせる場合は競合条件も決めてください。

最小APIを公開前に変える点
  • PUT・DELETEへ認証と権限確認を入れる
  • 1ファイルの最大容量をアプリ側で制限する
  • 利用者が指定したkeyをそのまま信用しない
  • 許可するMIME typeと拡張子を両方確認する

署名付きURLとCORSで直接アップロードする

大きな画像や動画をブラウザ→Worker→R2と中継すると、Workerが全byteを受け取ります。利用者を認証したWorkerが短時間の署名付きURLを発行し、ブラウザからR2へ直接PUTすれば、中継処理を減らせます。公式の署名付きURL資料では、有効期限を1秒から7日まで指定でき、GET・HEAD・PUT・DELETEに対応します。

URLは権限を含むBearer tokenです。API tokenやSecret Access Keyをブラウザへ渡すのではなく、信頼できるサーバー側で「この利用者が、このkeyへ、このMIME typeで、10分以内にPUTできる」というURLだけを発行します。漏れたURLは期限まで利用できるため、有効期限を必要以上に長くしないでください。

さらに、公式CORS資料によると、ブラウザから署名付きURLを使う場合はbucketのCORS設定が必要です。URLが正しくても、AllowedOriginsに本番originがなく、methodやheaderが許可されていなければブラウザが失敗します。localhostだけで成功し、本番ドメインで失敗する典型原因です。

Xでも、Abdulbasit氏はコースコンテンツのアップロードへR2の署名付きURLを使い、endpointとerrorのテスト、型付け、storage層の分離まで実装しています。署名URLを作ったことではなく、正常系と失敗系を分けて確認している点が参考になります。

直接uploadの成功判定
  • 未ログイン利用者は署名URLを取得できない
  • 許可originからPUTすると成功する
  • 期限切れURLと許可外originは失敗する
  • upload後にサイズ・MIME・所有者を記録できる

公開はr2.devとカスタムドメインを使い分ける

R2 bucketは初期状態で非公開です。公開方法は、開発確認用のr2.dev、本番配信用のカスタムドメイン、非公開オブジェクトを一時的に渡す署名付きURLの3つに分けます。誰にいつまで見せるかを決めてから入口を選びます。

公式のPublic buckets資料は、r2.devを非本番向けとしています。公式上限資料では可変レート制限があり、数百requests/秒を超えると429や帯域制限が起こり得ると説明しています。動作確認後の画像配信はassets.example.comのようなカスタムドメインへ移します。

カスタムドメインならCloudflare Cache、WAF、Access、Bot Managementを組み合わせられます。ただしAccessで保護したつもりでもr2.devを有効なまま残すと、開発URLから読める可能性があります。本番へ切り替えたらr2.devを無効化し、公開不要なbucketは最初からPublic accessを有効にしません。

Xでは、shin氏が自宅サーバーとR2を組み合わせた動画中継Rishabh氏が108MBのインストーラー配信を投稿しています。いずれもR2単体の設定値は公開されていませんが、R2がWeb画像だけでなく動画や配布物の経路にも使われている例です。

公開方法の選び方
  • 動作確認だけ: r2.dev
  • 一般公開の本番ファイル: カスタムドメイン
  • 会員限定・期限付き: 署名付きURLまたはWorker経由
  • 社内限定: カスタムドメイン+Cloudflare Access

Cloudflare R2の料金と運用上の注意点

接続後は、保存容量だけでなく操作回数と公開経路を運用へ含めます。R2はInternetへのegressが無料でも、すべての処理が無料になるわけではありません。アクセス数、ファイル数、保存期間から先に式を作ります。

Standard料金と無料枠を4項目で計算する

Cloudflare公式R2料金によると、Standardは保存0.015ドル/GB-month、Class Aは4.50ドル/100万requests、Class Bは0.36ドル/100万requests、Internetへのegressは無料です。毎月の無料枠は保存10GB-month、Class A 100万回、Class B 1,000万回で、Standardだけに適用されます。

料金項目Standard代表的な処理
保存0.015ドル/GB-month画像・動画・PDFを保持
Class A4.50ドル/100万回PUT・LIST・multipart upload
Class B0.36ドル/100万回GET・HEAD
egress無料Internetへデータ送信

例えば平均2MBの画像を1,000枚なら約2GBです。月内に1,000回PUTし、10万回GETする程度なら、他のR2利用がなければ各無料枠内です。一方、平均100KBのassetを10万個保存し、毎日1,000万回読まれる公式例では、保存は無料枠内でもClass Bが月104.40ドルになります。容量が小さいから0円とは限りません。

Xでは、Boring Stackが月次R2費用0.12ドルVenelin K.氏が400超のサイト・月間約4,500万requestsの構成でR2約1ドルと投稿しています。どちらも容量やClass内訳がないため同額になる保証はありませんが、R2以外のVPS、DNS、Cloudflare for SaaSなどの費用が別に残る点は実運用の参考になります。

egress無料で見落としやすい費用
  • R2の保存容量とClass A・B操作
  • WorkersのrequestsとCPU時間
  • 外部originからR2へ移す際の元サービス側egress
  • 独自ドメイン、変換サービス、監視など周辺費用

Infrequent Accessは30日と取得料を先に見る

Infrequent Accessは保存が0.01ドル/GB-monthとStandardより低い一方、Class Aは9ドル/100万回、Class Bは0.90ドル/100万回、取得は0.01ドル/GBです。最低保存期間は30日で、5日で削除しても30日分の保存料金がかかり、無料枠は適用されません。

そのため、頻繁に読むWeb画像、配布中のinstaller、毎日再生される動画を最初からIAへ置くと、保存差額より取得料と操作料が増えることがあります。初めてのbucketはStandardにし、30日以上保持し、ほとんど読まれないbackupや過去ログだけをIA候補にします。

公式Storage classes資料は、StandardをWeb・アプリ・mediaなど頻繁に読む用途、IAをarchive、backup、long-tail user content向けとしています。SoloTech氏は生成動画を長期間保存しない用途でR2の費用が小さいと投稿していますが、短期保存でIAを選ぶと最低30日の条件があるため、保存期間が短いこととIAが安いことは同義ではありません。

比較するときは、100GBを1か月置いた場合の保存差額だけならStandard 1.50ドル、IA 1.00ドルです。しかしIAから月50GBを読み出せば取得料だけで0.50ドルが加わり、操作単価も高くなります。保存差額が小さい規模では、読み出し量が少し増えるだけで逆転するため、実測前にIAへ固定しない方が安全です。

Storage classの判断
  • 月に何度も読む: Standard
  • 30日以上保持してほぼ読まない: IAを試算
  • 短期生成物: Standard+Lifecycle削除
  • 迷う段階: 無料枠があるStandardから開始

Class A・Class Bをアクセス数から見積もる

Class Aは状態を変える操作が中心で、PutObject、ListObjects、multipartの各part uploadなどが含まれます。Class BはGetObjectやHeadObjectなどの読み取りです。DeleteObject、DeleteBucket、AbortMultipartUploadは無料操作ですが、削除前に全objectを何度もLISTすれば、そのLISTはClass Aになります。

料金ページには端数切り上げも明記されています。100万1回の操作は200万回分として計算され、保存1.1GB-monthは2GB-monthとして請求される例です。上限の直前だけを見るのではなく、月次見積もりに1課金単位の余裕を持たせます。

見積もりは「新規upload数」「一覧取得数」「originまで届くGET数」「HEAD数」をログから数えます。カスタムドメインのCacheがhitすればR2のGETを減らせますが、cache missはR2へ到達します。画像1枚をHTML上で10回参照したことではなく、R2まで届いたoperationを料金画面とanalyticsで確認します。

Cloudflare公式Budget alertsでは、Pay-as-you-go accountに金額閾値のemail通知を設定できます。ただし通知は情報提供だけで、利用を停止せず、請求上限にもなりません。2026年7月から対象accountへ10ドルのdefault alertを順次追加する更新もありますが、日次処理のためリアルタイム停止装置としては使えません。

公開初日の費用監視
  • R2 analyticsで保存量と操作数を見る
  • cache hitとorigin GETを分ける
  • 5ドル・10ドルなど低いbudget alertを置く
  • アプリ側にもupload容量と回数制限を置く

キャッシュ・ライフサイクル・大容量uploadを整える

カスタムドメインへCloudflare Cacheを設定すると、edgeから配信でき、R2への読み取り回数を減らせます。ただし公式Consistency資料は、Cache経由では削除したobject、上書き前のobject、過去の404がTTLまで残ることがあると説明しています。同じURLで差し替えるならpurgeまでを更新処理へ含めるか、file名へhashを付けます。

一時fileにはObject Lifecycleを設定し、例としてtmp/配下を7日後、処理済みlogを90日後に削除します。長期保管はStandardからIAへ30日後に移すruleも作れます。手作業の定期削除より、prefixと保存期間を設定として残す方が再現できます。

大容量fileはupload方式も変わります。公式Limitsでは1object最大5TiB、single-part upload最大5GiB、multipart upload最大約4.995TiB、partsは最大10,000です。動画やbackupを1回のPUTへ詰め込まず、retryできるmultipartを使い、失敗したuploadをAbortして不要partsを残さないようにします。

また、同じobject keyへの同時書き込みは1秒1回が上限です。多数の利用者がlatest.jsonを更新する設計より、UUIDや時刻を含む別keyへ保存し、参照先のmetadataだけを更新する方が競合を減らせます。Cacheを使う公開assetならapp.20260827.cssのようなversion付きkeyが安全です。

運用で起こりやすい取り違え
  • R2直接操作の強整合とCache表示を同じだと思う
  • Lifecycle削除とIAの30日最低期間を混同する
  • 5GiB超をsingle-partでuploadする
  • 複数処理が同じkeyへ同時上書きする

R2で失敗したときの確認順と次の一歩

PUTできない場合は、bucket名、Binding名、tokenの対象bucket、Object Read & Write権限の順に確認します。S3 SDKを使うならendpointはhttps://<ACCOUNT_ID>.r2.cloudflarestorage.com、regionはautoです。Secret Access Keyは作成時しか再表示できず、ブラウザやGitへ入れてはいけません。

ブラウザだけ失敗するならCORS、公開URLだけ404ならPublic access・custom domain・object key・Cache済み404を調べます。uploadは成功するのに古い画像が見えるならR2ではなくCacheをpurgeします。料金が増えたら、容量より先にClass A・Bのoperationと、cache miss、LIST、HEAD、multipart partsを分解してください。

公式Durability資料ではR2は年11 ninesの耐久性を目標としますが、意図的・誤操作による削除は防げないと明記されています。重要fileはbucket-scoped tokenで権限を絞り、bucket lock、別accountや別serviceへのbackup、復元テストを組み合わせます。「壊れにくい」と「消せない」は別です。

最初の公開は、1つのStandard bucket、1つのBinding、1MB未満のtest imageから始めてください。PUT→GET→DELETE、custom domain、CORS、budget alertまで通ったら、利用者uploadやmultipartへ広げます。この順番なら、コード、権限、公開、料金のどこで止まったかを一段ずつ判定できます。

R2公開前の最終チェック
  • StandardでPUT・GET・DELETEを確認した
  • 本番はcustom domain、r2.devは無効化した
  • 署名URLは短時間、CORSはorigin限定にした
  • 容量・Class A・Class B・IA取得料を試算した
  • budget alertと誤削除対策を用意した

R2は、D1へ入れにくい画像やファイルをWorkersと同じCloudflare account内で扱うための有力な選択肢です。egress無料だけで決めず、公開経路とoperationを先に設計すれば、小さなWebアプリから配布・動画・backupまで段階的に広げられます。