> ## Content Index
> Fetch the complete content index at: https://blog.devutilites.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Cloudflare CDN y caché a fondo: qué se cachea, Cache Rules, Tiered Cache, purgas y Cache API
- URL: https://blog.devutilites.com/cloudflare-cdn-cache-a-fondo/
- Published: 2026-10-03T03:14:01.000Z
- Updated: 2026-10-03T03:14:01.000Z
- Description: 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.
- Author: Luis Collantes
- Tags: Cloudflare, DevOps

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.

🧪

**Las pruebas con `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.

![Esquema de la caché de Cloudflare: el visitante pide un fichero al centro de datos cercano; si está en caché se sirve; si no, se consulta un centro superior (Tiered Cache) y solo este llega al origen; Cache Reserve guarda copias persistentes en R2](https://blog.devutilites.com/content/images/2026/10/cf-cache-esquema.png)

Con Tiered Cache, solo los centros superiores hablan con tu origen. Diagrama: devutilites.

### 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-Control` con `private`, `no-store`, `no-cache` o `max-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:

```text
$ 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: MISS
```

Se 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.

🚨

**Cachear HTML de páginas con sesión es peligroso.** Si una regla cachea `/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:

```typescript
// 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):

```text
$ 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.put` **no es compatible con Tiered Cache**. Si quieres que el Worker aproveche la caché por niveles, haz `fetch()` al origen con opciones de caché en lugar de usar la Cache API.
- Las respuestas con `Set-Cookie` **no se guardan nunca**; quita la cabecera o usa `Cache-Control: private=Set-Cookie`.
- `cache.put` devuelve un **413** si `Cache-Control` impide 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-Cookie` en 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](https://blog.devutilites.com/cloudflare-a-fondo/): la visión general de la plataforma.
- [Cloudflare DNS a fondo](https://blog.devutilites.com/cloudflare-dns-a-fondo/): la nube naranja que activa la caché.
- [Cloudflare WAF y protección DDoS a fondo](https://blog.devutilites.com/cloudflare-waf-ddos-a-fondo/): la seguridad del mismo proxy.
- [Cloudflare Workers KV a fondo](https://blog.devutilites.com/cloudflare-workers-kv-a-fondo/): cuando necesitas una caché global y no por centro de datos.
- [Cloudflare R2 a fondo](https://blog.devutilites.com/cloudflare-r2-a-fondo/): el almacenamiento sobre el que se monta Cache Reserve.