R2 vs S3 per un progetto indie: egress, API e il costo che non vedi in fattura

Lo storage costa poco su R2 e su S3. Cambia l'uscita dei dati: gratis su R2, $0,09 per GB su S3 Milano dopo i primi 100 GB. Listini ufficiali a confronto, tre scenari di costo ipotetici, le funzioni dell'API S3 che R2 non implementa, data location e migrazione con Sippy. Dalla documentazione ufficiale, senza fatture vere.

Francesco Pio Nocerino8 minCloudflare
R2 vs S3 per un progetto indie: egress, API e il costo che non vedi in fattura
Indice

Questo è un confronto per chi deve scegliere dove mettere file, immagini, upload degli utenti o backup di un progetto piccolo: Cloudflare R2 o Amazon S3. Non è il racconto di una migrazione e non ci sono fatture vere. I prezzi vengono dal listino ufficiale di R2 e dalla Price List API di AWS per S3, letti il 27 ottobre 2026. Gli esempi di costo sono ipotetici e servono a vedere dove si sposta la spesa, non a promettere una cifra.

In breve:

  • Lo storage costa poco su tutti e due. R2 Standard costa $0,015 per GB al mese, S3 Standard a Milano $0,024 (i primi 50 TB).
  • La differenza vera è l'uscita dei dati verso Internet. Su R2 l'egress è gratis per ogni classe di storage. Su S3, dopo i primi 100 GB al mese gratuiti aggregati su tutto l'account AWS, a Milano paghi $0,09 per GB.
  • Le richieste costano simile: su R2 $4,50 per milione di scritture e $0,36 per milione di letture, su S3 Milano $5,30 e $0,40.
  • R2 parla l'API S3, ma non tutta: mancano bucket policy, ACL, versioning, tagging degli oggetti e replica via API S3. Il costo che non vedi in fattura è il codice e la configurazione che devi toccare.
  • Se i tuoi file li legge quasi solo altro codice dentro la stessa region AWS, il vantaggio di R2 sull'egress si riduce molto.

Cosa stai pagando davvero

Un object storage ha quattro voci di costo: lo spazio occupato, le scritture, le letture e i dati che escono. Le prime tre sono quasi sempre piccole per un progetto indie. La quarta dipende da quanto traffico fanno i tuoi file, e cresce con il successo del prodotto, non con la sua dimensione.

Per questo un confronto fatto solo sul prezzo al GB porta fuori strada. Un'app con 20 GB di immagini servite a molti utenti può spendere più in egress che in storage, molte volte.

I listini a confronto

Prezzi in dollari, letti il 27 ottobre 2026. Per S3 ho preso la region eu-south-1 (Milano) dalla Price List API di AWS, pubblicata il 15 ottobre 2026. L'egress viene dall'offerta AWSDataTransfer per la stessa region. Altre region costano diverso: Francoforte, per esempio, ha lo storage a $0,0245.

Voce

Cloudflare R2 Standard

Amazon S3 Standard (Milano)

Storage

$0,015 per GB al mese

$0,024 per GB al mese (primi 50 TB)

Scritture e liste

$4,50 per milione (Class A)

$0,0053 per 1.000 (PUT, COPY, POST, LIST), cioè $5,30 per milione

Letture

$0,36 per milione (Class B)

$0,0004 per 1.000 (GET e altre), cioè $0,40 per milione

Egress verso Internet

Gratis

Primi 100 GB al mese gratis su tutto l'account, poi $0,09 per GB fino a 10 TB

Incluso ogni mese

10 GB, 1 milione di Class A, 10 milioni di Class B

Solo i 100 GB di egress; per i nuovi account il piano gratuito oggi è a crediti

Classe infrequente

$0,01 per GB, $0,01 per GB letto, minimo 30 giorni

Standard-IA $0,0131 per GB, $0,01 per GB letto, minimo 30 giorni e 128 KB per oggetto

Due dettagli dal listino di R2 che contano su volumi piccoli. Cloudflare arrotonda all'unità successiva: 1,1 GB-mese viene fatturato come 2 GB-mese, un milione e un'operazione come due milioni. E la quota gratuita vale solo per lo storage Standard, non per Infrequent Access.

Sul lato AWS, la pagina dei prezzi di S3 elenca cosa non si paga in trasferimento: i dati in ingresso da Internet, i trasferimenti tra bucket della stessa region, quelli verso altri servizi AWS nella stessa region e quelli verso CloudFront. La stessa pagina dice che i clienti nell'Unione Europea possono chiedere tariffe ridotte di trasferimento per casi d'uso previsti dal Data Act, contattando il supporto. La pagina del Free Tier descrive per i nuovi account un piano con fino a $200 di crediti in 6 mesi.

Tre scenari ipotetici, calcolati sui listini

Sono numeri inventati per mostrare la forma della spesa. Non includono tasse, cambio euro/dollaro, CDN davanti allo storage o altri servizi AWS che usano la quota gratuita di egress.

Scenario A, piccolo sito con immagini. 50 GB salvati, 500 GB al mese scaricati dagli utenti, 1 milione di letture e 100.000 scritture.

  • S3 Milano: storage $1,20, egress (500 − 100 GB gratis) × $0,09 = $36,00, letture $0,40, scritture $0,53. Totale circa $38,13.
  • R2: storage (50 − 10 GB gratis) × $0,015 = $0,60, operazioni dentro la quota gratuita, egress $0. Totale circa $0,60.

Scenario B, app con upload degli utenti che cresce. 1 TB salvato, 5 TB al mese di download, 20 milioni di letture e 2 milioni di scritture.

  • S3 Milano: storage $24,00, egress 4.900 GB × $0,09 = $441,00, letture $8,00, scritture $10,60. Totale circa $483,60.
  • R2: storage 990 GB × $0,015 = $14,85, Class A (2 − 1 milioni gratis) = $4,50, Class B (20 − 10 milioni gratis) = $3,60, egress $0. Totale circa $22,95.

Scenario C, dati letti solo dentro AWS. Gli stessi 1 TB, ma li legge un job su EC2 nella stessa region e nessun file esce verso Internet. Su S3 il trasferimento verso servizi AWS della stessa region è gratuito, quindi resta storage più richieste: circa $42,60. Su R2 restano i $22,95, ma il tuo job su AWS leggerebbe i dati da fuori e l'architettura diventa più complicata. Qui la scelta non la fa l'egress, la fa dove gira il resto del tuo stack.

Il listino di R2 riporta anche un suo esempio di asset hosting: 100.000 file da 100 KB letti 10 milioni di volte al giorno costano $104,40 al mese, tutto in operazioni Class B. Con file piccoli e tante letture, il conto lo fanno le richieste, non i GB.

Il costo che non vedi in fattura: l'API

R2 implementa l'API S3, e con l'SDK di AWS cambi soprattutto l'endpoint. La documentazione di R2 per aws-sdk-js-v3 usa questa configurazione:

import { S3Client } from "@aws-sdk/client-s3";

const s3 = new S3Client({
  region: "auto", // richiesto dall'SDK, R2 non lo usa
  endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: ACCESS_KEY_ID,
    secretAccessKey: SECRET_ACCESS_KEY,
  },
});

La region di un bucket R2 è sempre auto; per gli strumenti che non permettono di indicarla, us-east-1 o un valore vuoto vengono trattati come auto. Le operazioni sugli oggetti che usi ogni giorno ci sono: GetObject, PutObject, HeadObject, DeleteObject, ListObjects, CopyObject e l'upload multipart. Anche gli URL presigned sono supportati.

Il problema sta nel resto. La pagina S3 API compatibility, aggiornata il 14 ottobre 2026, segna come non implementate via API S3:

  • bucket policy, ACL e PublicAccessBlock;
  • versioning del bucket;
  • tagging degli oggetti (GetObjectTagging, PutObjectTagging);
  • replica, logging, inventory, configurazione delle notifiche;
  • configurazione di Object Lock.

Alcune di queste funzioni R2 le ha, ma con strumenti suoi. Le event notifications mandano messaggi a una coda di Cloudflare Queues, non a SNS o Lambda. I bucket locks si configurano da dashboard, Wrangler o API di Cloudflare. L'accesso pubblico passa da un dominio personalizzato collegato al bucket, non da una bucket policy.

Quindi la domanda giusta non è "R2 è compatibile con S3?", ma "il mio codice usa solo la parte compatibile?". Se il progetto fa upload, download e URL firmati, il cambio è piccolo. Se dipende da policy IAM sul bucket, versioning per il ripristino o eventi verso Lambda, quella parte va riscritta, ed è lavoro che nessun listino mostra.

Dentro un Worker non serve l'SDK

Se il tuo backend gira già su Cloudflare Workers, R2 si usa con un binding, senza chiavi nel codice. La reference della Workers API di R2 espone metodi come get, put, delete e list sul binding:

export default {
  async fetch(request, env) {
    const key = new URL(request.url).pathname.slice(1);
    const obj = await env.MY_BUCKET.get(key);
    if (!obj) return new Response("Not found", { status: 404 });
    return new Response(obj.body, {
      headers: { etag: obj.httpEtag },
    });
  },
};

La documentazione avverte che l'SDK di AWS dentro un Worker richiede il flag nodejs_compat e, per alcune operazioni che leggono XML, un alias per @aws-sdk/xml-builder. Se ti servono solo letture e scritture, il binding evita il problema. È lo stesso schema descritto nel pezzo su Workers, D1 e R2 come stack serverless.

Servire file pubblici: r2.dev non è per la produzione

Su R2 puoi esporre un bucket in due modi. Il sottodominio r2.dev è pensato per i test: secondo la pagina dei limiti ha un rate limit variabile, e oltre qualche centinaio di richieste al secondo risponde 429. Per la produzione la documentazione indica un dominio personalizzato, che ti dà cache, regole e analytics di Cloudflare.

Su S3 il percorso tipico per servire file pubblici è CloudFront davanti al bucket. Il trasferimento da S3 a CloudFront non si paga, ma CloudFront ha un suo listino che qui non ho confrontato. Se stai valutando questa strada, quello è il numero da aggiungere allo scenario.

Dove stanno i dati

Su S3 scegli la region, e per un pubblico italiano Milano è la scelta ovvia. Su R2 la posizione di default è automatica: viene scelta la region disponibile più vicina a chi crea il bucket. Puoi dare un location hint (per esempio weur, Europa occidentale), ma la documentazione sulla data location lo definisce un "best effort", non una garanzia.

Se ti serve una garanzia per il GDPR, R2 ha le Jurisdictional Restrictions: il bucket resta dentro una giurisdizione, come l'Unione Europea. Con l'API S3 l'endpoint cambia in https://<ACCOUNT_ID>.<JURISDICTION>.r2.cloudflarestorage.com, e nei Worker va indicata la jurisdiction nel binding.

Sulla consistenza, la pagina del modello di consistenza di R2 dichiara letture dopo scrittura, cancellazioni e liste fortemente consistenti a livello globale. Le modifiche ai permessi delle chiavi API, invece, si propagano con un ritardo.

Se hai già tutto su S3

Cloudflare documenta due strumenti di migrazione, gratuiti a parte le operazioni Class A che fanno su R2. Super Slurper copia tutto il bucket in una volta. Sippy copia un oggetto su R2 la prima volta che viene richiesto, così sposti per primi i file che fanno traffico.

In entrambi i casi il bucket di partenza può addebitarti l'egress della copia, come avverte il listino di R2. Con Sippy quella spesa si distribuisce sulle richieste che avresti pagato comunque. Per un progetto piccolo, il costo della migrazione dei dati di solito è minore del costo di riscrivere le parti di codice che usano funzioni S3 non supportate.

Quale scegliere

  • R2 se i file escono verso utenti su Internet: immagini, video, download, asset di un'app. È anche la scelta naturale se il backend è già su Workers o se stai decidendo tra Vercel, Cloudflare e un VPS e Cloudflare è in testa.
  • S3 se i dati li consumano soprattutto servizi AWS nella stessa region, se ti servono versioning, bucket policy IAM o eventi verso Lambda, o se il resto dell'infrastruttura è già lì.
  • Tutti e due è un'opzione reale: originali su S3 dove li elabora il tuo stack AWS, copia pubblica su R2 per servirli. Ha senso solo quando l'egress di S3 è una voce che vedi già in fattura.

Per i backup, qualunque scelta va accompagnata da un piano di ripristino provato. Il pezzo su come fare il backup di D1 usa R2 come destinazione degli export e mostra il ragionamento.

Cosa conviene fare adesso

  • Apri la fattura o il Cost Explorer di AWS e separa la voce di data transfer da quella di storage. Se il trasferimento è la voce più grande, il confronto con R2 merita una prova.
  • Cerca nel codice le chiamate a funzioni che R2 non supporta via API S3: PutBucketPolicy, versioning, PutObjectTagging, notifiche. Se non ne trovi, il cambio è soprattutto di endpoint.
  • Rifai i tre scenari con i tuoi numeri: GB salvati, GB scaricati al mese, letture e scritture. Usa i listini ufficiali del giorno, perché cambiano.
  • Se scegli R2 per file pubblici, collega un dominio personalizzato prima di andare in produzione e lascia r2.dev ai test.

Lo storage costa poco ovunque. Quello che cambia è quanto ti costa ogni file che esce e quanto codice devi toccare il giorno in cui vuoi cambiare fornitore.

[ DOMANDE FREQUENTI ]

Domande frequenti

01R2 è davvero senza costi di egress?

Sì, per l'uscita dei dati da R2. Il listino dice che l'egress diretto da R2, tramite Workers API, API S3 o domini r2.dev, non viene addebitato, per ogni classe di storage. Paghi storage e operazioni. Se colleghi altri servizi a consumo al bucket, quei servizi possono addebitarti i loro costi.

02Posso usare l'SDK di AWS con R2?

Sì. Imposti l'endpoint https://<ACCOUNT_ID>.r2.cloudflarestorage.com, la region auto e le chiavi generate per R2. Funzionano le operazioni sugli oggetti, l'upload multipart e gli URL presigned. Non funzionano via API S3 bucket policy, ACL, versioning, tagging degli oggetti e replica.

03Quanto costa S3 in Italia?

Nella region di Milano, letta il 27 ottobre 2026: $0,024 per GB al mese di storage Standard per i primi 50 TB, $0,0053 per 1.000 scritture, $0,0004 per 1.000 letture. L'egress verso Internet costa $0,09 per GB fino a 10 TB, dopo i primi 100 GB gratuiti al mese aggregati su tutto l'account.

04Quando conviene S3 invece di R2?

Quando i dati restano dentro AWS: un trasferimento verso servizi AWS della stessa region è gratuito, quindi l'egress non pesa. Conviene anche quando dipendi da funzioni che R2 non espone via API S3, come versioning, bucket policy o notifiche verso Lambda.

05Il piano gratuito di R2 basta per un side project?

Spesso sì: ogni mese include 10 GB di storage Standard, 1 milione di operazioni Class A e 10 milioni di Class B, con egress gratuito. La quota non vale per Infrequent Access, e l'uso viene arrotondato all'unità di fatturazione successiva.

06Come sposto i dati da S3 a R2 senza fermare il sito?

Con Sippy: abiliti la migrazione sul bucket R2 e ogni oggetto richiesto che non c'è ancora viene servito dal bucket S3 e copiato su R2. Per il resto dei dati usi Super Slurper. S3 può addebitarti l'egress delle copie.

Condividi