Costruire un proprio server CDN: prelevare una volta, consegnare mille volte
Un server CDN preleva ogni file una volta dall'origine e poi lo consegna mille volte da sé. Il server di origine vede solo una frazione delle richieste, i visitatori ottengono tempi di caricamento brevi. Ecco come costruirne uno.
Ogni file che un visitatore carica dal tuo sito, il tuo server di origine lo preleva dal filesystem, lo impacchetta in una risposta e lo spedisce. Con dieci visitatori al giorno non se ne accorge nessuno. Con diecimila visitatori contemporanei è esattamente questo il motivo per cui il sito diventa lento, anche se l'applicazione vera e propria non ha nulla da fare.
Un server CDN si mette davanti. Preleva ogni file una volta dall'origine e poi lo consegna da sé, tutte le volte che serve. L'origine vede così solo una frazione delle richieste, e i visitatori ricevono i loro file da un server che non fa altro che consegnare. Questo articolo mostra quanto porti davvero, come si configura un server di questo tipo con nginx e cosa conta in termini di traffico e sede.
Che cosa fa davvero un server CDN
Il funzionamento si racconta in tre frasi:
- Un visitatore richiede un file al server CDN.
- Se è già presente, parte subito. Questo è un hit.
- Se non è presente, il server CDN la preleva dall'origine, la memorizza e la consegna. Questo è un miss, e per ogni file avviene una sola volta per ciascun periodo di validità.
Il principio è lo stesso di un server di streaming: prelevare una volta, consegnare molte volte. La differenza è solo che un server di streaming distribuisce un flusso di dati in corso, mentre un server CDN distribuisce file già pronti.
Il conto: quanto alleggerisce una cache
Decisiva è la hit rate, cioè la quota di richieste a cui risponde la cache stessa. Con un milione di richieste al giorno l'alleggerimento è questo:
| Hit rate | Richieste all'origine | Alleggerimento |
|---|---|---|
| 50 % | 500.000 | fattore 2 |
| 80 % | 200.000 | fattore 5 |
| 95 % | 50.000 | fattore 20 |
| 99 % | 10.000 | fattore 100 |
Per i file statici il 95 per cento e oltre è la normalità, perché immagini, script e font cambiano raramente. Il salto dal 95 al 99 per cento vale più di quanto i numeri lascino intuire: riduce il carico residuo ancora a un quinto.
Allo stesso tempo il traffico si sposta. L'origine trasferisce quasi nulla, il server CDN tanto di più. Con 500.000 richieste di immagini al giorno da 400 KB ciascuna si tratta di 200 GB al giorno, cioè circa 6 TB al mese, solo per le immagini. Per questo in un nodo CDN il numero importante non è la CPU, ma connettività e volume.
Quando conviene un server CDN proprio
I provider CDN commerciali fatturano per gigabyte trasferito. È comodo e, con poco volume, conveniente. Oltre una certa quantità il rapporto si capovolge:
| Caso | Server CDN proprio | Fatturazione per gigabyte |
|---|---|---|
| Pochi GB al mese | conviene poco | più conveniente |
| Molti TB al mese | prezzo mensile fisso | cresce in modo lineare |
| Download grandi, archivi video | chiaramente in vantaggio | diventa presto costosa |
| Controllo completo su regole e protocolli | completo | limitato |
A questo si aggiunge un punto difficile da quantificare: con un nodo proprio decidi tu cosa viene messo in cache, per quanto tempo e quando sparisce. Non sei legato alle regole di un provider esterno e puoi consegnare anche contenuti che negli schemi altrui non rientrano.
Architettura con nginx come cache
Installazione
apt update
apt install -y nginx
Non serve altro, la cache è integrata in nginx. Crea la directory in cui andranno i file:
mkdir -p /var/cache/cdn
chown www-data:www-data /var/cache/cdn
Configurazione
La cache viene descritta una volta nel blocco http e poi usata nei singoli location:
proxy_cache_path /var/cache/cdn
levels=1:2
keys_zone=cdn:100m
max_size=200g
inactive=30d
use_temp_path=off;
proxy_cache_key "$scheme$request_method$host$request_uri";
I valori nel dettaglio: keys_zone=cdn:100m basta per circa 800.000 voci, max_size limita lo spazio sul disco, inactive=30d butta via i file che nessuno ha richiesto per 30 giorni e use_temp_path=off evita uno spostamento inutile tra directory.
Sotto, il server vero e proprio:
server {
listen 443 ssl;
http2 on;
server_name cdn.example.com;
ssl_certificate /etc/letsencrypt/live/cdn.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cdn.example.com/privkey.pem;
location / {
proxy_pass https://origin.example.com;
proxy_set_header Host origin.example.com;
proxy_http_version 1.1;
proxy_cache cdn;
proxy_cache_valid 200 301 302 24h;
proxy_cache_valid 404 1m;
# raggruppa i miss simultanei in una sola richiesta
proxy_cache_lock on;
proxy_cache_lock_timeout 10s;
# continua a consegnare in caso di problemi all'origine
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
# i cookie di sessione non devono rovinare la cache
proxy_ignore_headers Set-Cookie;
proxy_hide_header Set-Cookie;
add_header X-Cache-Status $upstream_cache_status always;
}
}
Tre righe meritano attenzione particolare. proxy_cache_lock fa in modo che per un file nuovo non siano cento visitatori a interrogare l'origine contemporaneamente, ma uno solo, mentre gli altri attendono. proxy_cache_use_stale mantiene attiva la consegna mentre l'origine si riavvia o risponde lentamente. E l'eliminazione di Set-Cookie impedisce che una sola risposta con cookie di sessione renda inutilizzabile tutta la cache.
Verificare che funzioni
nginx -t && systemctl reload nginx
curl -sI https://cdn.example.com/bild.jpg | grep -i x-cache-status
curl -sI https://cdn.example.com/bild.jpg | grep -i x-cache-status
Alla prima chiamata compare MISS, alla seconda HIT. Se anche la seconda volta compare MISS, la causa è quasi sempre uno dei tre soliti sospetti: un Set-Cookie nella risposta, un Cache-Control: no-store dall'origine oppure l'indicazione di lunghezza mancante.
Cosa va in cache e cosa no
La soluzione pulita è che lo dica l'origine stessa. L'origine conosce i propri contenuti, la cache no. Per questo imposta gli header adeguati:
| Contenuto | Header all'origine |
|---|---|
| File con checksum nel nome | Cache-Control: public, max-age=31536000, immutable |
| Immagine o download senza versionamento | Cache-Control: public, max-age=86400 |
| HTML di una pagina pubblica | Cache-Control: public, max-age=300 |
| Area riservata, carrello | Cache-Control: private, no-store |
Se vuoi decidere nella cache stessa, lo puoi fare con location separate per tipo di file:
location ~* \.(jpg|jpeg|png|gif|webp|avif|svg|ico|css|js|woff2|mp4|zip)$ {
proxy_pass https://origin.example.com;
proxy_cache cdn;
proxy_cache_valid 200 7d;
add_header Cache-Control "public, max-age=604800";
add_header X-Cache-Status $upstream_cache_status always;
}
Rimuovere contenuti in modo mirato
La via più semplice è non dover cancellare affatto: chi dà un nome di file nuovo a ogni immagine modificata, per esempio logo.a1b2c3.png, non ha il problema. Il file vecchio esce dalla cache da sé, prima o poi.
Se non è possibile, con nginx aiuta il modulo libero ngx_cache_purge. Senza modulo aggiuntivo il percorso si calcola dalla chiave di cache, perché nginx ne usa la somma MD5 come nome del file:
KEY="httpsGETcdn.example.com/bild.jpg"
H=$(printf '%s' "$KEY" | md5sum | cut -d' ' -f1)
rm -f "/var/cache/cdn/${H: -1}/${H: -3:2}/$H"
Per l'uso quotidiano basta di solito un piccolo script che esegue queste righe per un elenco di percorsi.
File grandi e video
Con i file oltre alcune centinaia di megabyte ti serve la suddivisione in blocchi, altrimenti a ogni salto nel video la cache scarica di nuovo l'intero file:
location /video/ {
proxy_pass https://origin.example.com;
proxy_cache cdn;
proxy_cache_valid 200 206 7d;
slice 1m;
proxy_set_header Range $slice_range;
proxy_cache_key "$uri$slice_range";
}
Così nginx preleva solo i blocchi davvero richiesti. Se uno spettatore salta a metà di un film, sulla linea passano pochi megabyte invece di diversi gigabyte.
TLS, HTTP/2 e HTTP/3
Un nodo CDN consegna molti file piccoli a molte connessioni, ed è proprio lì che i protocolli più recenti rendono. HTTP/2 si risolve con http2 on;. HTTP/3 porta vantaggi soprattutto sulle connessioni mobili, perché un cambio di rete non fa cadere la connessione:
listen 443 quic reuseport;
listen 443 ssl;
http2 on;
add_header Alt-Svc 'h3=":443"; ma=86400';
Non dimenticare di aprire la UDP 443 nel firewall. È il motivo più frequente per cui HTTP/3 risulta configurato e nessuno lo usa comunque.
Più sedi
Un singolo nodo porta già la parte maggiore del vantaggio, cioè l'alleggerimento dell'origine. Il secondo vantaggio, percorsi brevi verso il visitatore, arriva solo con più sedi.
La distribuzione passa dal DNS: lo stesso nome punta a un nodo diverso a seconda della provenienza del visitatore. Ti serve per questo un provider DNS con assegnazione geografica. Tutti i nodi ricevono la stessa configurazione e la stessa origine, non sanno nulla l'uno dell'altro, ed è proprio questo a rendere l'architettura così robusta: se un nodo cade, lo togli dal DNS e gli altri subentrano.
KernelHost offre sedi in Germania, sulla costa ovest degli Stati Uniti, sulla costa est degli Stati Uniti e inoltre a Singapore e in Corea del Sud. Una panoramica con le latenze la trovi alla pagina Sedi dei server. Come regola empirica: un nodo per ogni continente su cui si trova una parte rilevante del tuo pubblico.
Misurare invece di indovinare
Scrivi lo stato della cache nel log, così puoi contare la hit rate in qualsiasi momento:
log_format cdn '$remote_addr $status $body_bytes_sent '
'$upstream_cache_status "$request"';
access_log /var/log/nginx/cdn.log cdn;
L'analisi si fa con una sola riga:
awk '{print $4}' /var/log/nginx/cdn.log | sort | uniq -c | sort -rn
Se la quota di HIT sui file statici sta sotto il 90 per cento, cercare la causa conviene quasi sempre: in genere sono cookie nelle risposte, validità troppo brevi oppure richieste con parametri variabili alla fine dell'indirizzo, che per la cache sembrano ogni volta un file nuovo.
Quale server serve
Un nodo CDN è una macchina da consegna. L'ordine delle caratteristiche importanti è quindi diverso rispetto a un server applicativo:
- Traffico e connettività. È qui che nasce tutto il carico. Un nodo in esercizio trasferisce regolarmente diversi terabyte al mese.
- Disco. NVMe è chiaramente in vantaggio, perché la cache è fatta di molti accessi piccoli e casuali. La dimensione dipende da quanta parte dei tuoi contenuti è calda, non da quanto siano grandi i contenuti nel complesso.
- RAM. Il sistema operativo usa la memoria libera come seconda cache davanti al disco. Qui più RAM significa direttamente più velocità.
- CPU. La meno critica, serve solo per il TLS, e i processori moderni lo gestiscono senza sforzo.
I nostri server root a traffico illimitato sono fatti esattamente per questo caso: 2 × 1 Gbit/s reali per server senza limitazioni, volume illimitato a prezzo mensile fisso, NVMe datacenter in RAID e 3,2 Tbps di protezione antiflood di serie. L'attivazione richiede meno di 30 secondi, con accesso root completo, IPv4 e una rete IPv6 /64. Poiché la fatturazione è PrePaid e disdicibile ogni mese, puoi iniziare con un nodo e aggiungere in qualsiasi momento una seconda sede, quando il tuo pubblico si distribuisce.
Domande frequenti
Che cosa fa esattamente un server CDN?
Quanto alleggerisce davvero?
Mi serve per questo un provider commerciale?
Quali file conviene mettere in cache e quali no?
Per quanto tempo la cache deve conservare i file?
Come cancello un file dalla cache?
Che cosa succede se il server di origine non risponde?
Funziona anche con file video di grandi dimensioni?
Quante sedi mi servono?
Da che cosa capisco che la cache funziona?
2026 KernelHost GmbH. Tutti i diritti riservati. Questa guida è protetta dal diritto d'autore. La ripubblicazione su altri siti web, anche parziale o in forma modificata, non è consentita senza il nostro consenso scritto. Le citazioni con indicazione della fonte e un link sono le benvenute.

