Cloudflare CDN y caché a fondo: qué se cachea, Cache Rules, Tiered Cache, purgas y Cache API
Cómo decide Cloudflare qué cachear y durante cuánto, qué significa cada cf-cache-status, Cache Rules, Tiered Cache, Cache Reserve, purgas y la Cache API de Workers, con límites por plan, pruebas reales con curl y un Worker que cachea respuestas probado en local.
Contenido
La caché es la razón original de Cloudflare: guardar copias de tus ficheros en cientos de centros de datos para que el visitante las reciba del más cercano y tu servidor no tenga que servirlas una y otra vez. Funciona sola desde el primer día, pero con unas reglas por defecto que sorprenden a mucha gente. La más importante: Cloudflare no cachea tu HTML ni tu JSON salvo que se lo pidas.
En este artículo vemos cómo decide Cloudflare qué cachear y durante cuánto tiempo, cómo leer la cabecera cf-cache-status, las Cache Rules, el Tiered Cache, Cache Reserve, las purgas y la Cache API de Workers, con lo que incluye cada plan. Y lo comprobamos con curl contra este mismo blog y con un Worker que cachea respuestas en local.
curl se han hecho de verdad el 3 de octubre de 2026 contra este blog, que está detrás de Cloudflare. El Worker con la Cache API se ha ejecutado en local con Wrangler 4.147.0 y Node.js 24.21.0. Cache Rules, Tiered Cache, Cache Reserve y las purgas no los he probado: necesitan acceso a la configuración de una zona y no he tocado la del blog. Límites y precios comprobados en la documentación oficial ese mismo día.Cómo funciona
Cuando una petición llega a un centro de datos de Cloudflare para un registro con proxy (nube naranja), Cloudflare decide primero si la respuesta puede cachearse. Si puede, la busca en su caché local. Si está, la sirve (HIT). Si no, la pide al origen, la sirve y la guarda (MISS). Con Tiered Cache, antes de ir al origen pregunta a un centro de datos «superior» que agrupa las peticiones de muchos otros.

Qué se cachea por defecto
Cloudflare decide por extensión del fichero, no por tipo MIME. Por defecto cachea imágenes, CSS, JS, fuentes, PDF, vídeo, audio, comprimidos y unos cuantos formatos más (también el robots.txt). No cachea HTML ni JSON. Para cachear algo más, necesitas una Cache Rule.
Aunque la extensión sea cacheable, Cloudflare no guarda la respuesta si:
- el origen envía
Cache-Controlconprivate,no-store,no-cacheomax-age=0; - la respuesta trae
Set-Cookie; - el método no es
GET.
Y sí la guarda si Cache-Control es public con max-age mayor que 0, o si Expires es una fecha futura. Si hay max-age y Expires, gana max-age. Si el origen no manda ninguna de las dos, Cloudflare aplica un TTL por código de estado:
| Código | TTL por defecto |
|---|---|
| 200, 206, 301 | 120 min |
| 302, 303 | 20 min |
| 404, 410 | 3 min |
| Resto | No se cachea |
Para el CDN, s-maxage tiene prioridad sobre max-age: es la directiva pensada para cachés compartidas. Así puedes decir «el navegador, 30 segundos; Cloudflare, una hora».
Un detalle útil: si llegan muchas peticiones a la vez al mismo centro de datos para un fichero que no está en caché, Cloudflare solo manda una al origen y las demás esperan a esa respuesta (request collapsing).
Leer cf-cache-status
| Valor | Significado |
|---|---|
HIT | Servido desde la caché |
MISS | Cacheable, pero no estaba; se pidió al origen (y se guarda) |
EXPIRED | Estaba, pero caducado; se pidió de nuevo al origen |
STALE | Caducado, servido de caché porque el origen no respondía |
UPDATING | Caducado, servido de caché mientras se actualiza en segundo plano |
REVALIDATED | El origen confirmó con una petición condicional que no había cambiado |
BYPASS | Era cacheable, pero algo (cabeceras, una regla) hizo que no se cachease |
DYNAMIC | No es cacheable; se fue al origen sin mirar la caché |
Comprobado con curl
Este mismo blog está detrás de Cloudflare. La portada (HTML) y una hoja de estilos, pedida dos veces, y después con otra query string:
$ curl -sI https://blog.devutilites.com/ | grep -iE "^HTTP|cf-cache-status|cache-control"
HTTP/2 200
cache-control: public, max-age=0
cf-cache-status: DYNAMIC
$ curl -sI "https://blog.devutilites.com/public/cards.min.css?v=WwnU9jw5ancNC8Gc" | grep -iE "^HTTP|cf-cache-status|cache-control|^age"
HTTP/2 200
cache-control: public, max-age=31536000
cf-cache-status: MISS
$ curl -sI "https://blog.devutilites.com/public/cards.min.css?v=WwnU9jw5ancNC8Gc" | grep -iE "^HTTP|cf-cache-status|cache-control|^age"
HTTP/2 200
cache-control: public, max-age=31536000
age: 0
cf-cache-status: HIT
$ curl -sI "https://blog.devutilites.com/public/cards.min.css?v=prueba-21511" | grep -iE "^HTTP|cf-cache-status"
HTTP/2 200
cf-cache-status: MISSSe ve todo lo anterior: el HTML sale DYNAMIC (no se cachea por defecto), el CSS sale MISS la primera vez y HIT la segunda, y basta cambiar la query string para volver a MISS, porque la query string forma parte de la clave de caché. Es lo que aprovechan los temas que añaden ?v=… a sus ficheros para forzar que se descarguen de nuevo tras un cambio.
Cache Rules
Las Cache Rules usan el mismo lenguaje de reglas que el WAF: una expresión y lo que quieres hacer con las peticiones que la cumplen. Con ellas puedes:
- marcar como cacheable lo que no lo es por defecto (HTML, JSON de una API pública…) o al revés, excluir rutas;
- fijar el Edge TTL (cuánto guarda Cloudflare) y el Browser TTL (cuánto guarda el navegador), ignorando o respetando al origen;
- personalizar la clave de caché (ignorar la query string, incluir una cabecera o una cookie).
| Cache Rules | Free | Pro | Business | Enterprise |
|---|---|---|---|---|
| Número de reglas | 10 | 25 | 50 | 300 |
Requieren registros con proxy. Y una advertencia de la documentación: si la clave de caché incluye cabeceras, cookies u otras propiedades de la petición, la purga de un solo fichero desde el panel deja de funcionar, porque el panel no puede enviar esos valores.
/cuenta y la primera visita es de un usuario logueado, los siguientes visitantes pueden ver su página. Excluye siempre las rutas con contenido personal o usa una cookie de sesión para saltarte la caché.Tiered Cache
Sin Tiered Cache, cada centro de datos que no tiene un fichero se lo pide a tu origen. Con Tiered Cache, los centros se dividen en inferiores y superiores: los inferiores preguntan primero a un superior y solo los superiores hablan con tu origen. El resultado es más aciertos de caché y muchas menos conexiones a tu servidor.
| Tiered Cache | Free | Pro | Business | Enterprise |
|---|---|---|---|---|
| Tiered Cache con topología Smart | Sí | Sí | Sí | Sí |
| Generic Global, Regional, Custom | No | No | No | Sí |
La topología Smart elige automáticamente el mejor centro superior para tu origen según la latencia medida. Es gratis y conviene activarla. La documentación avisa de que, si cambias la IP de tu origen, puede cambiar el centro superior asignado y verás más MISS durante un tiempo.
Cache Reserve
La caché normal es efímera: los ficheros poco pedidos se expulsan. Cache Reserve añade una capa persistente montada sobre R2 para que tus ficheros cacheables sigan ahí aunque se pidan poco. Requiere un plan de pago de Cache Reserve y se factura por uso:
| Concepto | Precio |
|---|---|
| Almacenamiento | 0,015 $ por GB y mes |
| Operaciones de clase A (escrituras, en cada fallo de caché) | 4,50 $ por millón |
| Operaciones de clase B (lecturas desde Cache Reserve) | 0,36 $ por millón |
Las purgas son gratis. Merece la pena con muchos ficheros grandes que se piden poco y un origen caro o lento.
Purgar la caché
Puedes purgar por URL (lo recomendado), por hostname, por cache tag, por prefijo o todo. Todas las opciones están en todos los planes, pero con límites distintos:
| Purga | Free | Pro | Business | Enterprise |
|---|---|---|---|---|
| Por URL | 800 URL/s | 1500 URL/s | 1500 URL/s | 3000 URL/s |
| Hostname, tag, prefijo o todo | 5 peticiones/min | 5 peticiones/s | 10 peticiones/s | 50 peticiones/s |
En vez de purgar, a menudo es mejor versionar los ficheros (app.3f9a.js o ?v=…): el fichero nuevo tiene otra clave y el antiguo caduca solo.
La Cache API de Workers
Desde un Worker puedes usar la caché directamente con la Cache API (caches.default), por ejemplo para cachear respuestas que genera el propio Worker. Este ejemplo simula una API lenta y guarda su respuesta:
// Simula una API lenta (por ejemplo, un tipo de cambio que se calcula en el origen)
async function calcularTipos(): Promise<Response> {
await new Promise((r) => setTimeout(r, 300));
return Response.json(
{ eurUsd: 1.17, generado: new Date().toISOString() },
// max-age para el navegador; s-maxage para cachés compartidas como la de Cloudflare
{ headers: { "Cache-Control": "public, max-age=30, s-maxage=60" } },
);
}
export default {
async fetch(request, env, ctx): Promise<Response> {
const url = new URL(request.url);
if (url.pathname !== "/tipos") return new Response("No encontrado", { status: 404 });
// La caché solo guarda GET; la clave es la URL (incluida la query string)
const cache = caches.default;
const clave = new Request(url.toString(), { method: "GET" });
const enCache = await cache.match(clave);
if (enCache) {
const r = new Response(enCache.body, enCache);
r.headers.set("X-Cache", "HIT");
return r;
}
const respuesta = await calcularTipos();
// Guardar en segundo plano sin retrasar la respuesta
ctx.waitUntil(cache.put(clave, respuesta.clone()));
const r = new Response(respuesta.body, respuesta);
r.headers.set("X-Cache", "MISS");
return r;
},
} satisfies ExportedHandler<Env>;En local, Wrangler simula la caché. Resumen de tres peticiones (código, tiempo, cabeceras relevantes y cuerpo):
$ curl -s -D - -w "%{http_code} %{time_total}s\n" localhost:8807/tipos
200 0.331360s
Cache-Control: public, max-age=30, s-maxage=60
X-Cache: MISS
{"eurUsd":1.17,"generado":"2026-10-02T22:48:29.433Z"}
$ curl -s -D - -w "%{http_code} %{time_total}s\n" localhost:8807/tipos
200 0.006209s
Cache-Control: public, max-age=30, s-maxage=60
X-Cache: HIT
{"eurUsd":1.17,"generado":"2026-10-02T22:48:29.433Z"}
$ curl -s -D - -w "%{http_code} %{time_total}s\n" localhost:8807/tipos?moneda=gbp
200 0.305726s
Cache-Control: public, max-age=30, s-maxage=60
X-Cache: MISS
{"eurUsd":1.17,"generado":"2026-10-02T22:48:29.768Z"}La primera tarda 330 ms (la API lenta), la segunda 6 ms y devuelve el mismo generado, y otra query string vuelve a ser un fallo de caché. Lo que dice la documentación y conviene tener presente:
- La Cache API es local al centro de datos: lo que guardas en Madrid no existe en París.
cache.putno es compatible con Tiered Cache. Si quieres que el Worker aproveche la caché por niveles, hazfetch()al origen con opciones de caché en lugar de usar la Cache API.- Las respuestas con
Set-Cookieno se guardan nunca; quita la cabecera o usaCache-Control: private=Set-Cookie. cache.putdevuelve un 413 siCache-Controlimpide cachear o la respuesta es demasiado grande.- En el editor del panel y en el Playground, la Cache API no tiene efecto.
Errores comunes (y cómo evitarlos)
- Esperar que el HTML se cachee solo. No se cachea por defecto; necesitas una Cache Rule.
- Cachear páginas personalizadas. Excluye las rutas con sesión o salta la caché cuando haya cookie.
Set-Cookieen ficheros estáticos. Un framework que añade una cookie a todo hace que nada se cachee. Revisa las cabeceras del origen.- Purgar todo en cada despliegue. En Free solo tienes 5 peticiones de purga masiva por minuto, y la caché vacía dispara la carga del origen. Versiona los ficheros.
- Query strings aleatorias. Un parámetro de seguimiento (
?utm_…) crea una entrada nueva por valor. Ignóralo en la clave de caché con una Cache Rule. - No activar Tiered Cache. Es gratis y reduce mucho las peticiones al origen.
- Fiarse de la Cache API como caché global. Es por centro de datos; para datos compartidos, usa KV.
Cuándo usar cada cosa
Siempre: deja que el CDN cachee los estáticos, versiónalos y activa Tiered Cache Smart. Cache Rules cuando quieras cachear HTML o JSON públicos, ajustar TTL o limpiar la clave de caché. Cache Reserve si tienes mucho contenido de cola larga y un origen caro. Cache API cuando el contenido lo genera un Worker y merece la pena reutilizarlo en ese centro de datos.
Sigue leyendo
- Cloudflare a fondo: la visión general de la plataforma.
- Cloudflare DNS a fondo: la nube naranja que activa la caché.
- Cloudflare WAF y protección DDoS a fondo: la seguridad del mismo proxy.
- Cloudflare Workers KV a fondo: cuando necesitas una caché global y no por centro de datos.
- Cloudflare R2 a fondo: el almacenamiento sobre el que se monta Cache Reserve.