Montar un servidor CDN propio: recoger una vez, entregar mil veces
Un servidor CDN recoge cada archivo una vez del origen y lo entrega después mil veces él mismo. El servidor de origen ve solo una fracción de las peticiones y los visitantes obtienen tiempos de carga cortos. Así montas uno.
Cada archivo que un visitante carga de tu página lo saca tu servidor de origen del sistema de archivos, lo mete en una respuesta y lo manda. Con diez visitantes al día nadie lo nota. Con diez mil visitantes simultáneos, eso es precisamente la razón de que la página se vuelva lenta, aunque la aplicación en sí no tenga nada que hacer.
Un servidor CDN se coloca por delante. Recoge cada archivo una vez del origen y lo entrega después él mismo, tantas veces como haga falta. El origen ve por eso solo una fracción de las peticiones, y los visitantes reciben sus archivos de un servidor que no hace nada más que entregar. Este artículo muestra cuánto aporta eso de verdad, cómo montas un servidor así con nginx y qué importa en cuanto a tráfico y ubicación.
Qué hace realmente un servidor CDN
El proceso se cuenta en tres frases:
- Un visitante pide un archivo al servidor CDN.
- Si ya está allí, sale de inmediato. Eso es un acierto.
- Si no está allí, el servidor CDN la recoge del origen, la guarda y la entrega. Eso es un fallo, y ocurre por archivo solo una vez por periodo de validez.
El principio es el mismo que en un servidor de streaming: recoger una vez, entregar muchas. La única diferencia es que un servidor de streaming reparte un flujo de datos en marcha y un servidor CDN, archivos terminados.
La cuenta: cuánto alivia una caché
Lo decisivo es la tasa de aciertos, es decir la parte de las peticiones que responde la propia caché. Con un millón de peticiones al día, el alivio se ve así:
| Tasa de aciertos | Peticiones en el origen | Alivio |
|---|---|---|
| 50 % | 500.000 | factor 2 |
| 80 % | 200.000 | factor 5 |
| 95 % | 50.000 | factor 20 |
| 99 % | 10.000 | factor 100 |
Para los archivos estáticos, el 95 por ciento y más es lo normal, porque las imágenes, los scripts y las fuentes cambian poco. El salto del 95 al 99 por ciento es además más valioso de lo que dejan suponer las cifras: divide la carga restante otra vez entre cinco.
Al mismo tiempo, el tráfico se desplaza. El origen transmite ya casi nada, y el servidor CDN tanto más. Con 500.000 descargas de imágenes al día de 400 KB cada una, eso son 200 GB diarios, o sea unos 6 TB al mes, solo en imágenes. Por eso, en un nodo CDN la cifra importante no es la CPU, sino el enlace y el volumen.
Cuándo vale la pena un servidor CDN propio
Los proveedores comerciales de CDN facturan por gigabyte transferido. Eso es cómodo y, con poco volumen, barato. A partir de cierta cantidad, la relación se da la vuelta:
| Caso | Servidor CDN propio | Facturación por gigabyte |
|---|---|---|
| Pocos GB al mes | apenas vale la pena | más barata |
| Muchos TB al mes | precio mensual fijo | crece de forma lineal |
| Descargas grandes, archivos de vídeo | claramente en ventaja | se vuelve cara enseguida |
| Control total sobre reglas y registros | completo | limitado |
A esto se añade un punto difícil de cuantificar: con un nodo propio decides tú mismo qué se cachea, cuánto tiempo y cuándo desaparece. No estás atado a las reglas de un proveedor ajeno y puedes entregar también cosas que no encajan en esquemas ajenos.
Montaje con nginx como caché
Instalar
apt update
apt install -y nginx
No hace falta más, la caché viene integrada en nginx. Crea el directorio en el que deben quedar los archivos:
mkdir -p /var/cache/cdn
chown www-data:www-data /var/cache/cdn
Configurar
La caché se describe una vez en el bloque http y se usa después en cada uno de los distintos bloques 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";
Los valores significan en detalle: keys_zone=cdn:100m alcanza para unas 800.000 entradas, max_size limita el espacio en el disco, inactive=30d tira los archivos que nadie ha pedido en 30 días, y use_temp_path=off evita un traslado innecesario entre directorios.
Debajo, el servidor propiamente dicho:
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;
# agrupar varios fallos simultáneos en una sola petición
proxy_cache_lock on;
proxy_cache_lock_timeout 10s;
# seguir entregando si hay problemas en el origen
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
# las cookies de sesión no deben reventar la caché
proxy_ignore_headers Set-Cookie;
proxy_hide_header Set-Cookie;
add_header X-Cache-Status $upstream_cache_status always;
}
}
Tres líneas de ahí merecen atención especial. proxy_cache_lock hace que, con un archivo nuevo, no pidan cien visitantes al origen a la vez, sino solo uno, mientras los demás esperan. proxy_cache_use_stale mantiene la entrega en marcha mientras el origen se reinicia o responde despacio. Y ocultar Set-Cookie evita que una única respuesta con cookie de sesión deje inservible toda la caché.
Comprobar si surte efecto
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
En la primera llamada aparece ahí MISS; en la segunda, HIT. Si la segunda vez vuelve a aparecer MISS, casi siempre es cosa de uno de los tres sospechosos habituales: un Set-Cookie en la respuesta, un Cache-Control: no-store del origen o la falta del dato de longitud.
Qué se cachea y qué no
La solución limpia es que lo diga el propio origen. Él conoce sus contenidos, la caché no. Para eso envía las cabeceras adecuadas:
| Contenido | Cabecera en el origen |
|---|---|
| Archivo con suma de comprobación en el nombre | Cache-Control: public, max-age=31536000, immutable |
| Imagen o descarga sin versionado | Cache-Control: public, max-age=86400 |
| HTML de una página pública | Cache-Control: public, max-age=300 |
| Área con sesión iniciada, carrito | Cache-Control: private, no-store |
Si quieres decidirlo en la caché misma, se hace con bloques location propios por tipo de archivo:
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;
}
Quitar contenidos de forma selectiva
El camino más sencillo es no tener que borrar nada: quien da a cada imagen modificada un nombre de archivo nuevo, por ejemplo logo.a1b2c3.png, no tiene ese problema. El archivo antiguo sale de la caché por sí solo en algún momento.
Si eso no es posible, en nginx ayuda el módulo libre ngx_cache_purge. Sin módulo adicional, la ruta se puede calcular a partir de la clave de caché, porque nginx usa su suma MD5 como nombre de archivo:
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"
Para el día a día basta casi siempre un pequeño script que ejecute estas líneas para una lista de rutas.
Archivos grandes y vídeos
Con archivos de más de unos cientos de megabytes necesitas la división en bloques, porque si no la caché vuelve a cargar el archivo completo en cada salto dentro del vídeo:
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";
}
Así, nginx recoge solo los bloques que de verdad se piden. Si un espectador salta a la mitad de una película, por la línea viajan unos pocos megabytes en lugar de varios gigabytes.
TLS, HTTP/2 y HTTP/3
Un nodo CDN entrega muchos archivos pequeños a muchas conexiones, y justo ahí es donde se amortizan los protocolos más nuevos. HTTP/2 queda resuelto con http2 on;. HTTP/3 aporta sobre todo en conexiones de telefonía móvil, porque un cambio de red no corta la conexión:
listen 443 quic reuseport;
listen 443 ssl;
http2 on;
add_header Alt-Svc 'h3=":443"; ma=86400';
No olvides abrir además el UDP 443 en el firewall. Es la razón más frecuente de que HTTP/3 esté configurado y aun así nadie lo use.
Varias ubicaciones
Un solo nodo aporta ya la mayor parte de la ganancia, es decir el alivio del origen. La segunda ganancia, los caminos cortos hasta el visitante, llega solo con varias ubicaciones.
El reparto va por DNS: el mismo nombre apunta a un nodo distinto según la procedencia del visitante. Necesitas para eso un proveedor de DNS con asignación geográfica. Todos los nodos reciben la misma configuración y el mismo origen, no saben nada el uno del otro, y precisamente eso hace que el montaje sea tan robusto: si un nodo cae, lo quitas del DNS y los demás se hacen cargo.
KernelHost ofrece ubicaciones en Alemania, en la costa oeste de EE. UU., en la costa este de EE. UU. y además en Singapur y Corea del Sur. Encontrarás un resumen con latencias en Ubicaciones de servidores. Como regla general: un nodo por cada continente en el que esté una parte apreciable de tu público.
Medir en lugar de adivinar
Escribe el estado de la caché en el registro, así puedes contar la tasa de aciertos en cualquier momento:
log_format cdn '$remote_addr $status $body_bytes_sent '
'$upstream_cache_status "$request"';
access_log /var/log/nginx/cdn.log cdn;
Evaluarlo se puede con una sola línea:
awk '{print $4}' /var/log/nginx/cdn.log | sort | uniq -c | sort -rn
Si la proporción de HIT en archivos estáticos está por debajo del 90 por ciento, buscar la causa vale la pena casi siempre: suelen ser cookies en las respuestas, periodos de validez demasiado cortos o peticiones con parámetros cambiantes al final de la dirección, que para la caché parecen cada vez un archivo nuevo.
Qué servidor encaja
Un nodo CDN es una máquina de entrega. El orden de las propiedades importantes es por eso distinto que en un servidor de aplicaciones:
- Tráfico y enlace. Aquí se genera toda la carga. Un nodo transmite en funcionamiento varios terabytes al mes con regularidad.
- Disco. NVMe está claramente en ventaja, porque la caché consiste en muchos accesos aleatorios pequeños. El tamaño depende de cuánto de tu fondo está caliente, no de lo grande que sea el fondo en total.
- RAM. El sistema operativo usa la memoria libre como segunda caché por delante del disco. Más RAM significa aquí directamente más velocidad.
- CPU. Lo menos crítico, solo se necesita para TLS, y eso lo hacen los procesadores modernos de pasada.
Justo para este caso están hechos nuestros servidores root con tráfico ilimitado: 2 × 1 Gbit/s reales por servidor sin limitación de velocidad, volumen ilimitado a precio mensual fijo, NVMe de centro de datos en RAID y 3,2 Tbps de protección antifloods de serie. La puesta a disposición dura menos de 30 segundos, con acceso root completo, IPv4 y una red IPv6 /64. Como la facturación es de prepago y cancelable mensualmente, puedes empezar con un nodo y añadir en cualquier momento una segunda ubicación, cuando tu público se reparta.
Preguntas frecuentes
¿Qué hace exactamente un servidor CDN?
¿Cuánto alivia eso en realidad?
¿Necesito para eso un proveedor comercial?
¿Qué archivos debo cachear y cuáles no?
¿Cuánto tiempo debe guardar la caché los archivos?
¿Cómo borro un archivo de la caché?
¿Qué pasa si el servidor de origen se cae?
¿Funciona también con archivos de vídeo grandes?
¿Cuántas ubicaciones necesito?
¿Cómo reconozco que la caché funciona?
2026 KernelHost GmbH. Todos los derechos reservados. Esta guía está protegida por derechos de autor. Su publicación en otros sitios web, aunque sea de forma parcial o modificada, no está permitida sin nuestro consentimiento por escrito. Las citas con indicación de la fuente y un enlace son muy bienvenidas.

