Bygg en egen CDN-server: hämta en gång, leverera tusenfalt
En CDN-server hämtar varje fil en gång från ursprunget och levererar den därefter tusenfalt själv. Ursprungsservern ser bara en bråkdel av förfrågningarna, och besökarna får korta laddningstider. Så bygger du en.
Varje fil som en besökare laddar från din sida hämtar din ursprungsserver ur filsystemet, packar in den i ett svar och skickar iväg den. Vid tio besökare om dagen märker ingen det. Vid tiotusen samtidiga besökare är precis det skälet till att sidan blir långsam, trots att den egentliga applikationen inte har något att göra.
En CDN-server ställer sig framför. Den hämtar varje fil en gång från ursprunget och levererar den därefter själv, så ofta som den behövs. Ursprunget ser därigenom bara en bråkdel av förfrågningarna, och besökarna får sina filer från en server som inte gör något annat än att leverera. Den här artikeln visar hur mycket det verkligen ger, hur du sätter upp en sådan server med nginx och vad som är viktigt när det gäller trafik och plats.
Vad en CDN-server verkligen gör
Förloppet går att berätta i tre satser:
- En besökare frågar efter en fil hos CDN-servern.
- Ligger den redan där går den ut omedelbart. Det är en träff.
- Ligger den inte där hämtar CDN-servern den hos ursprunget, lägger undan den och levererar den. Det är en miss, och den inträffar bara en gång per fil och giltighetstid.
Principen är densamma som hos en streamingserver: hämta en gång, leverera många gånger. Skillnaden är bara att en streamingserver fördelar en löpande dataström och en CDN-server färdiga filer.
Räkningen: hur mycket en cache avlastar
Avgörande är träffkvoten, alltså andelen förfrågningar som cachen besvarar själv. Vid en miljon förfrågningar om dagen ser avlastningen ut så här:
| Träffkvot | Förfrågningar hos ursprunget | Avlastning |
|---|---|---|
| 50 % | 500 000 | Faktor 2 |
| 80 % | 200 000 | Faktor 5 |
| 95 % | 50 000 | Faktor 20 |
| 99 % | 10 000 | Faktor 100 |
För statiska filer är 95 procent och mer normalfallet, för bilder, skript och typsnitt ändras sällan. Steget från 95 till 99 procent är därvid mer värdefullt än siffrorna låter förmoda: det minskar den återstående lasten till en femtedel en gång till.
Samtidigt förskjuts trafiken. Ursprunget överför nästan ingenting längre, CDN-servern desto mer. Vid 500 000 bildhämtningar om dagen med 400 KB var blir det 200 GB dagligen, alltså omkring 6 TB i månaden, bara för bilder. Därför är det inte processorn som är den viktiga siffran vid en CDN-nod, utan anslutning och volym.
När en egen CDN-server lönar sig
Kommersiella CDN-leverantörer fakturerar per överförd gigabyte. Det är bekvämt och billigt vid liten volym. Från en viss mängd vänder förhållandet:
| Fall | Egen CDN-server | Fakturering per gigabyte |
|---|---|---|
| Några GB i månaden | lönar sig knappt | billigare |
| Många TB i månaden | fast månadspris | växer linjärt med |
| Stora nedladdningar, videoarkiv | klart i fördel | blir snabbt dyrt |
| Full kontroll över regler och loggar | fullständig | begränsad |
Dessutom finns en punkt som är svår att sätta siffror på: vid en egen nod bestämmer du själv vad som cachas, hur länge, och när det försvinner. Du är inte bunden till en utomstående leverantörs regler och kan även leverera sådant som inte passar in i andras scheman.
Uppbyggnad med nginx som cache
Installera
apt update
apt install -y nginx
Mer behövs inte, cachen är inbyggd i nginx. Lägg upp katalogen där filerna ska ligga:
mkdir -p /var/cache/cdn
chown www-data:www-data /var/cache/cdn
Konfigurera
Cachen beskrivs en gång i http-blocket och används sedan på de enskilda platserna:
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";
Värdena betyder i detalj: keys_zone=cdn:100m räcker för omkring 800 000 poster, max_size begränsar utrymmet på disken, inactive=30d slänger filer som ingen har frågat efter på 30 dagar, och use_temp_path=off undviker en onödig flytt mellan kataloger.
Därunder den egentliga servern:
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;
# bunta flera samtidiga missar till en enda förfrågan
proxy_cache_lock on;
proxy_cache_lock_timeout 10s;
# fortsätt leverera vid problem hos ursprunget
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
# sessionscookies får inte spränga cachen
proxy_ignore_headers Set-Cookie;
proxy_hide_header Set-Cookie;
add_header X-Cache-Status $upstream_cache_status always;
}
}
Tre rader därur förtjänar särskild uppmärksamhet. proxy_cache_lock ser till att inte hundra besökare frågar ursprunget samtidigt vid en ny fil, utan bara en, medan de övriga väntar. proxy_cache_use_stale håller leveransen uppe medan ursprunget startar om eller svarar långsamt. Och att gömma Set-Cookie hindrar att ett enda svar med sessionscookie gör hela cachen oanvändbar.
Kontrollera att det griper
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
Vid det första anropet står det MISS där, vid det andra HIT. Står det MISS även andra gången ligger det nästan alltid hos en av de tre vanliga misstänkta: en Set-Cookie i svaret, ett Cache-Control: no-store från ursprunget eller en saknad längdangivelse.
Vad som cachas och vad som inte gör det
Den rena lösningen är att ursprunget säger det själv. Det känner sitt innehåll, cachen gör det inte. Till det sätter det passande headers:
| Innehåll | Header hos ursprunget |
|---|---|
| Fil med kontrollsumma i namnet | Cache-Control: public, max-age=31536000, immutable |
| Bild eller nedladdning utan versionering | Cache-Control: public, max-age=86400 |
| HTML på en offentlig sida | Cache-Control: public, max-age=300 |
| Inloggat område, varukorg | Cache-Control: private, no-store |
Vill du bestämma i cachen själv går det via egna platser per filtyp:
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;
}
Bli av med innehåll riktat
Den enklaste vägen är att inte behöva ta bort något alls: den som ger varje ändrad bild ett nytt filnamn, till exempel logo.a1b2c3.png, har inte problemet. Den gamla filen faller så småningom ur cachen av sig själv.
Går det inte hjälper hos nginx den fria modulen ngx_cache_purge. Utan extramodul går sökvägen att räkna ut från cachenyckeln, eftersom nginx använder dess MD5-summa som filnamn:
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"
I vardagen räcker oftast ett litet skript som kör de här raderna för en lista med sökvägar.
Stora filer och videor
Vid filer över några hundra megabyte behöver du uppdelningen i block, annars laddar cachen om hela filen vid varje hopp i videon:
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";
}
Därmed hämtar nginx bara de block som verkligen efterfrågas. Hoppar en tittare in i mitten av en film vandrar några få megabyte över ledningen i stället för flera gigabyte.
TLS, HTTP/2 och HTTP/3
En CDN-nod levererar många små filer till många anslutningar, och precis där lönar sig de nyare protokollen. HTTP/2 är avklarat med http2 on;. HTTP/3 ger framför allt något på mobilanslutningar, eftersom ett nätbyte inte river anslutningen:
listen 443 quic reuseport;
listen 443 ssl;
http2 on;
add_header Alt-Svc 'h3=":443"; ma=86400';
Glöm inte att öppna UDP 443 i brandväggen. Det är det vanligaste skälet till att HTTP/3 är inrättat och ingen använder det ändå.
Flera platser
En enda nod ger redan största delen av vinsten, nämligen avlastningen av ursprunget. Den andra vinsten, korta vägar till besökaren, kommer först med flera platser.
Fördelningen löper över DNS: samma namn pekar på olika noder beroende på var besökaren kommer ifrån. Du behöver en DNS-leverantör med geografisk tilldelning för det. Alla noder får samma konfiguration och samma ursprung, de vet ingenting om varandra, och precis det gör uppbyggnaden så robust: faller en nod ifrån tar du den ur DNS, och de övriga tar över.
KernelHost erbjuder platser i Tyskland, på USA:s västkust, på USA:s östkust samt i Singapore och Sydkorea. En översikt med latenser hittar du under Serverplatser. Som tumregel: en nod per kontinent där en nämnvärd del av din publik sitter.
Mäta i stället för att gissa
Skriv cachestatusen till loggen, då kan du räkna ut träffkvoten när som helst:
log_format cdn '$remote_addr $status $body_bytes_sent '
'$upstream_cache_status "$request"';
access_log /var/log/nginx/cdn.log cdn;
Det går att utvärdera med en enradare:
awk '{print $4}' /var/log/nginx/cdn.log | sort | uniq -c | sort -rn
Ligger andelen HIT under 90 procent vid statiska filer lönar det sig nästan alltid att söka orsaken: oftast är det cookies i svaren, för korta giltighetstider eller förfrågningar med skiftande parametrar i slutet av adressen, som för cachen ser ut som en ny fil varje gång.
Vilken server passar
En CDN-nod är en leveransmaskin. Ordningen på de viktiga egenskaperna ser därför annorlunda ut än hos en applikationsserver:
- Trafik och anslutning. Här uppstår hela lasten. En nod överför i drift regelbundet flera terabyte i månaden.
- Disk. NVMe är klart i fördel, eftersom cachen består av många små slumpmässiga åtkomster. Storleken rättar sig efter hur mycket av ditt bestånd som är varmt, inte efter hur stort beståndet är totalt.
- RAM. Operativsystemet använder ledigt minne som en andra cache framför disken. Mer RAM betyder här direkt mer fart.
- Processor. Minst kritisk, den behövs bara för TLS, och det klarar moderna processorer i förbifarten.
Precis för det här fallet är våra rootservrar med obegränsad trafik gjorda: riktiga 2 × 1 Gbit/s per server utan strypning, obegränsad volym till fast månadspris, datacenter-NVMe i RAID och 3,2 Tbps floodskydd från fabrik. Driftsättningen tar under 30 sekunder, med full root-åtkomst, IPv4 och ett /64-IPv6-nät. Eftersom faktureringen sker på PrePaid-basis och går att säga upp varje månad kan du börja med en nod och när som helst lägga till en andra plats, om din publik fördelar sig.
Vanliga frågor
Vad gör en CDN-server egentligen?
Hur mycket avlastar det i praktiken?
Behöver jag en kommersiell leverantör för det?
Vilka filer ska jag cacha och vilka inte?
Hur länge ska cachen behålla filer?
Hur tar jag bort en fil från cachen?
Vad händer om ursprungsservern faller ifrån?
Fungerar det även för stora videofiler?
Hur många platser behöver jag?
Hur ser jag att cachen fungerar?
2026 KernelHost GmbH. Alla rättigheter förbehållna. Den här guiden är skyddad av upphovsrätt. Publicering på andra webbplatser, helt, delvis eller i bearbetad form, är inte tillåten utan vårt skriftliga medgivande. Citat med källhänvisning och länk är uttryckligen välkomna.

