S3-compatible object storage su Hetzner e Contabo: backup e asset management per Laravel

S3-compatible object storage su Hetzner e Contabo: backup e asset management per Laravel

Il primo mese dopo aver configurato un sistema di backup automatico su Amazon S3 per un cliente del settore e-commerce, la fattura AWS è arrivata a 145 euro - per un backup giornaliero di 40 GB di database dump + 120 GB di immagini prodotto. Il cliente aveva previsto un budget di 30 euro al mese per i backup cloud. La discrepanza nasceva dai costi di trasferimento dati (egress) che AWS addebita separatamente dallo storage: ogni volta che il sistema verificava l'integrità dei backup scaricando un campione, pagava l'egress. E l'egress di AWS costa 0,09 euro per GB - un prezzo che su 160 GB di backup verificati settimanalmente produce un costo ricorrente che il cliente non aveva previsto.

La soluzione è stata migrare da AWS S3 a Hetzner Object Storage - un servizio S3-compatible con data center in Europa (Falkenstein, Germania), costo di storage di 0,0065 euro per GB/mese (contro 0,023 di AWS S3 Standard), e nessun costo di egress all'interno della rete Hetzner. Per i 160 GB del cliente, il costo mensile è passato da 145 euro (AWS con egress) a 1,04 euro (Hetzner senza egress) - una riduzione del 99,3%. Il codice Laravel non è stato modificato: grazie al driver Flysystem S3, il cambio è stato una modifica di tre righe nel file .env (endpoint, access key e secret key), senza toccare una riga di codice PHP.

In questo articolo ti mostro come configurare Hetzner Object Storage e Contabo Object Storage come backend per backup automatici e gestione asset statici in Laravel, con i dettagli di configurazione, le considerazioni di sicurezza e il confronto di costi reale che uso per decidere quale provider scegliere per ciascun cliente.

Quanto costa davvero l'object storage S3-compatible europeo rispetto ad AWS?

Il confronto dei costi è la prima analisi che faccio per ogni cliente che usa o vuole usare l'object storage, perché la differenza tra i provider è significativa e non riguarda solo il prezzo per GB - riguarda i costi nascosti di egress, le chiamate API e le operazioni di listing che AWS fattura separatamente.

ComponenteAWS S3 StandardHetzner Object StorageContabo Object Storage
Storage per TB/mese23,00€6,50€2,49€ (incluso 250GB gratis)
Egress per TB90,00€0€ (rete Hetzner)0€ (primi 5TB gratis)
PUT/COPY per 10.0000,005€InclusoIncluso
GET per 10.0000,0004€InclusoIncluso
Data centerIrlanda, FrancoforteGermania (Falkenstein)Germania, UK
GDPR complianceSì (EU region)Nativo (DE)Nativo (DE)
Costo 160GB + egress 50GB/mese~8,30€ + 4,50€ = 12,80€1,04€0,40€

Il risparmio annuo per il cliente e-commerce con 160 GB di backup + 50 GB di egress mensile: da 153,60 euro su AWS a 12,48 euro su Hetzner - 141 euro risparmiati l'anno. Non è un importo che cambia la vita di un'azienda, ma moltiplicato per 5 clienti e considerando la crescita dei dati nel tempo, il risparmio cumulativo diventa rilevante. E per i clienti con volumi più grandi (1-5 TB di asset, tipici per e-commerce con cataloghi fotografici), il risparmio è nell'ordine di migliaia di euro l'anno.

Per nuovi clienti Hetzner, puoi ottenere €20.00 di credito gratuito utilizzando questo link con codice sconto - una scelta ideale per aziende europee che necessitano conformità GDPR.

Nel mio profilo professionale trovi il dettaglio dell'esperienza che porto nella gestione di infrastrutture cloud multi-provider - la scelta del provider per ogni componente (compute, storage, CDN) è una delle decisioni architetturali che impatto maggiore ha sul costo operativo a lungo termine.

Configurazione Laravel con Flysystem: il cambio che non tocca il codice

Laravel usa Flysystem come layer di astrazione per il filesystem - il che significa che qualsiasi storage S3-compatible è accessibile con lo stesso codice usato per AWS S3, cambiando solo la configurazione. La configurazione per Hetzner Object Storage nel file config/filesystems.php è identica a quella di AWS S3 con l'aggiunta dell'endpoint che punta al server Hetzner:

// config/filesystems.php - disco Hetzner Object Storage
'hetzner' => [
    'driver' => 's3',
    'key' => env('HETZNER_OS_KEY'),
    'secret' => env('HETZNER_OS_SECRET'),
    'region' => env('HETZNER_OS_REGION', 'fsn1'),
    'bucket' => env('HETZNER_OS_BUCKET'),
    'endpoint' => env('HETZNER_OS_ENDPOINT', 'https://fsn1.your-objectstorage.com'),
    'use_path_style_endpoint' => true,
],

Il parametro use_path_style_endpoint è fondamentale: Hetzner e Contabo usano il path-style URL (https://endpoint/bucket/key) invece del virtual-hosted-style di AWS (https://bucket.s3.region.amazonaws.com/key). Senza questo parametro, le richieste falliscono con un errore DNS perché il client tenta di risolvere bucket.fsn1.your-objectstorage.com come hostname - un dominio che non esiste.

Una volta configurato, l'utilizzo nel codice Laravel è identico a qualsiasi altro disco Flysystem - Storage::disk('hetzner')->put(), ->get(), ->delete(), ->url() funzionano esattamente come con il disco S3 di AWS. Per il sistema di backup del cliente, lo script di backup notturno che caricava il dump MySQL su S3 ha richiesto una sola modifica: cambiare Storage::disk('s3') in Storage::disk('hetzner'). Il resto del codice - generazione del dump, compressione, cifratura GPG, upload, verifica integrità - è rimasto identico.

Backup automatici: lo script che uso su ogni server

Il sistema di backup che installo su ogni server dei clienti usa l'object storage come destinazione remota con retention configurabile. Il flusso è: dump MySQL compresso e cifrato, upload sull'object storage, verifica integrità tramite hash SHA-256, pulizia dei backup locali vecchi, e notifica dello stato (successo o fallimento) via webhook al canale di monitoring. Ho descritto la strategia completa di backup nel mio articolo sul piano di disaster recovery per applicazioni PHP, dove l'object storage è una delle tre copie nella regola 3-2-1-1-0. Il vantaggio dell'object storage rispetto allo storage a blocchi (Hetzner Volume) per i backup è che l'object storage ha durabilità nativa (i dati sono replicati su più nodi nel datacenter), non richiede montaggio come filesystem, e supporta il lifecycle management per la retention automatica - i backup vecchi vengono eliminati automaticamente dopo N giorni senza bisogno di un cronjob di pulizia.

Sicurezza dell'object storage: accesso privato, policy e cifratura

Un aspetto che molti tutorial trascurano è la sicurezza dell'object storage. Di default, un bucket su Hetzner Object Storage è privato - nessuno può accedere ai file senza le credenziali. Ma quando configuri un bucket per servire immagini pubbliche di un e-commerce, devi rendere il bucket o i singoli file pubblici - il che significa che chiunque con l'URL può accedere al file. Per le immagini prodotto, questo è il comportamento desiderato. Per i backup del database, assolutamente no.

La best practice che implemento è la separazione in due bucket con policy diverse: un bucket backup-privato con accesso esclusivamente privato (nessun file accessibile via URL pubblico, solo via API con credenziali), e un bucket assets-pubblico con accesso in lettura pubblica per i file media. Le credenziali (access key e secret key) sono diverse per i due bucket - il che significa che una compromissione delle credenziali del bucket pubblico (ad esempio, se qualcuno trova la secret key in un commit accidentale) non dà accesso ai backup.

La cifratura dei backup prima dell'upload è un altro layer che aggiungo sistematicamente. Anche se il bucket è privato e le credenziali sono sicure, la cifratura con GPG garantisce che anche in caso di accesso non autorizzato al bucket (compromissione delle credenziali, bug nel provider, ordine giudiziario di un paese terzo), i dati restano illeggibili senza la chiave di decifratura che vive solo sul server del cliente. Lo script di backup che uso cifra il dump MySQL con gpg --encrypt prima dell'upload e decifra con gpg --decrypt durante il ripristino - un overhead di pochi secondi su un dump di 40 GB che garantisce la confidenzialità dei dati indipendentemente dalla sicurezza del provider di storage.

Per i clienti che rientrano nel perimetro NIS2, la cifratura a riposo dei backup è un requisito di conformità - non un'opzione. Ho documentato i requisiti tecnici NIS2 per gli sviluppatori nel mio articolo sugli obblighi tecnici della direttiva per applicazioni web, dove la strategia di backup cifrato è uno degli elementi valutati durante gli audit. L'object storage con cifratura pre-upload soddisfa questo requisito nativamente, perché i dati cifrati transitano e risiedono sullo storage del provider senza che il provider abbia mai accesso al contenuto in chiaro.

Un ultimo aspetto di sicurezza riguarda la retention e l'immutabilità. Hetzner Object Storage supporta l'Object Lock - la possibilità di impostare un periodo di retention durante il quale i file non possono essere cancellati o sovrascritti, nemmeno dall'account proprietario del bucket. Questa funzionalità è critica per i backup ransomware-resistant: se un attaccante compromette il server e le credenziali dell'object storage, non può cancellare i backup se sono protetti da Object Lock. Ho descritto il pattern completo di backup ransomware-resistant con la regola 3-2-1-1-0 nel mio articolo sulla simulazione di impatto ransomware per PMI.

Asset management: immagini prodotto su object storage con CDN

Il secondo caso d'uso dell'object storage per i clienti e-commerce è la gestione delle immagini prodotto. Un catalogo con 50.000 prodotti e 3-5 immagini per prodotto produce 150.000-250.000 file immagine per un totale di 80-200 GB. Salvare queste immagini sul disco del VPS ha tre problemi: primo, consuma spazio disco che potrebbe servire al database e ai log; secondo, il backup del VPS include le immagini (che non cambiano quasi mai), aumentando inutilmente il tempo e il costo del backup; terzo, se il VPS ha un problema (disco corrotto, reinstallazione), le immagini vanno perso insieme al server.

Con l'object storage, le immagini sono su un servizio separato, accessibili via URL pubblico, e il VPS può essere reinstallato da zero senza perdere le immagini. La configurazione in Laravel è un disco Flysystem dedicato alle immagini, con un URL pubblico configurato per servire le immagini direttamente dall'object storage (o attraverso un CDN come Cloudflare per il caching edge):

La migrazione delle immagini esistenti dal filesystem locale all'object storage è un job batch che ho standardizzato: legge la directory delle immagini, carica ogni file sull'object storage preservando il path relativo, aggiorna il percorso nel database (da /storage/images/product_42.jpg a https://cdn.esempio.it/images/product_42.jpg), e alla fine elimina le copie locali. Per il cliente e-commerce con 200.000 immagini, la migrazione ha richiesto 3 ore di upload e ha liberato 150 GB di spazio sul disco del VPS - spazio che è stato immediatamente utile per l'espansione del database e dei log applicativi.

Il costo dell'object storage per le immagini del catalogo è la voce di spesa più bassa dell'intero stack: 200 GB su Hetzner costano 1,30 euro al mese. Con il traffico delle immagini servito attraverso Cloudflare (che cacha i file statici gratuitamente), l'egress dall'object storage è minimo perché la maggior parte delle richieste viene servita dalla cache CDN senza toccare il backend. Se gestisci un'applicazione Laravel con immagini e backup su disco locale e vuoi migrare verso object storage europeo per ridurre i costi e migliorare la resilienza, contattami per una sessione di migrazione: in mezza giornata configuriamo l'object storage, migriamo i file, aggiorniamo i percorsi nel database e verifichiamo che tutto funzioni - un intervento che si ripaga nel primo mese con il disco liberato e il costo di backup ridotto. L'object storage europeo è una di quelle scelte infrastrutturali che costano pochissimo, riducono i rischi significativamente, e una volta implementate non richiedono manutenzione - il tipo di investimento che ogni PMI dovrebbe fare come baseline della propria infrastruttura IT.

Ultima modifica: