AWS 3 ago 2026 · 6 min de lectura

CloudFront delante de tu app: por qué tu hit rate es del 30%

Yohangel Ramos

Yohangel Ramos

Tech Lead · Senior Fullstack Developer

Poner CloudFront delante de una app es de esas decisiones que todo el mundo aplaude en la reunión y casi nadie verifica después. Se crea la distribución, el dominio apunta ahí, el primer test da mejor latencia y se asume que "ya está cacheado". Meses más tarde abres las métricas y el hit rate está en 30%: siete de cada diez peticiones siguen llegando a tu origen, pagas transferencia dos veces y encima tienes una capa más donde depurar cuando algo sale raro. No es que el CDN no funcione. Es que cachear no es activar una casilla: es decidir tres cosas —qué identifica a una respuesta, cuánto vive y cómo se sustituye— y esas tres decisiones son tuyas, no de AWS.

La cache key es el 90% del problema

Un CDN guarda respuestas indexadas por una clave. Si dos peticiones generan la misma clave, la segunda es un hit. Todo lo que metas en esa clave multiplica el número de entradas posibles y divide tu hit rate.

El caso que más veces he visto: alguien reenvía la cookie de sesión al origen "porque la app la necesita" y, sin querer, la mete también en la cache key. A partir de ahí cada usuario tiene su propia copia de cada recurso. Técnicamente hay caché; en la práctica no cachea nada. Lo mismo pasa con reenviar todas las query strings: los utm_source de una campaña convierten una URL en cincuenta.

La pieza que arregla esto es entender que CloudFront tiene dos políticas distintas y separadas: la cache policy define qué entra en la clave, y la origin request policy define qué se le manda al origen. No son lo mismo. La cookie puede viajar al origen sin fragmentar la caché.

resource "aws_cloudfront_cache_policy" "estaticos" {
  name        = "static-assets"
  min_ttl     = 0
  default_ttl = 86400
  max_ttl     = 31536000

  parameters_in_cache_key_and_forwarded_to_origin {
    enable_accept_encoding_brotli = true
    enable_accept_encoding_gzip   = true

    cookies_config { cookie_behavior = "none" }
    headers_config { header_behavior = "none" }

    query_strings_config {
      query_string_behavior = "whitelist"
      query_strings { items = ["v"] }
    }
  }
}

Regla que aplico sin excepciones: la cache key empieza vacía y se le añade lo que se pueda justificar. Nunca al revés.

El Cache-Control manda; la distribución solo pone límites

Los TTL de la distribución confunden a mucha gente porque parecen la configuración principal, y no lo son. default_ttl solo se aplica cuando el origen no manda cabeceras de caché. max_ttl es un techo. min_ttl, un suelo. El que decide de verdad es tu origen, ruta por ruta.

Y ahí está la cabecera que más rendimiento me ha dado por carácter escrito: s-maxage.

# Bundle con hash en el nombre: inmutable de verdad
Cache-Control: public, max-age=31536000, immutable

# HTML de una página que cambia: el navegador no lo guarda, el CDN sí
Cache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=86400

max-age habla con el navegador; s-maxage habla con las cachés compartidas —el CDN— y tiene prioridad sobre el anterior. Esa separación es lo que te permite publicar cambios rápido: el navegador del usuario no retiene nada, el edge retiene cinco minutos, y purgar el edge está bajo tu control. Purgar el navegador de alguien no lo está, nunca.

💡 Si tu HTML sale del origen con max-age=3600, no tienes un problema de CDN: tienes usuarios con una versión vieja durante una hora y ninguna forma de arreglarlo. max-age=0, s-maxage=3600 cachea igual de bien y sí se puede revertir.

Invalidar es el plan B; versionar es el plan A

Las invalidaciones son la herramienta que todo el mundo usa primero y la que menos debería hacer falta. Son asíncronas, tardan, tienen las primeras 1.000 rutas al mes gratis y luego se facturan, y sobre todo: un /* en cada despliegue vacía la caché entera y manda todo el tráfico a tu origen a la vez, justo en el momento en el que acabas de desplegar. Es la peor combinación posible.

La alternativa es que la invalidación deje de ser necesaria. Si tus assets llevan el hash del contenido en el nombre, un despliegue no modifica ficheros: publica ficheros nuevos, con URLs nuevas, que nadie tiene cacheadas. No hay nada que invalidar. Lo único que cambia de sitio es el documento de entrada, y ese ya tiene un s-maxage corto.

Cuando aun así hace falta, la acoto:

# Plan B, y acotado: solo los documentos de entrada
aws cloudfront create-invalidation \
  --distribution-id E2XXXXXXXXXXXX \
  --paths '/index.html' '/blog/*'

stale-while-revalidate: que el origen deje de sufrir

Sin stale-while-revalidate, el momento en que expira el TTL es un acantilado: la petición que llega justo después espera a que el origen responda entero. Con tráfico alto, varias peticiones llegan a la vez y todas van al origen por la misma URL.

Con SWR, el edge sirve la copia caducada inmediatamente y revalida por detrás. El usuario nunca paga la latencia del refresco, y tu p99 deja de tener picos periódicos que no correlacionan con nada. Añade stale-if-error y además tienes un modo degradado gratis: si el origen devuelve 5xx, se sigue sirviendo lo último bueno en lugar de propagar el error.

Si el origen sigue notando el tráfico de revalidación, Origin Shield añade una capa de caché intermedia que consolida esas peticiones. Es un coste extra: lo activo cuando los datos lo piden, no por defecto.

Cómo lo mido antes de tocar nada

La métrica CacheHitRate de la consola sirve para saber que tienes un problema, no para saber dónde. Para eso están los logs: cada línea trae x-edge-result-type con Hit, RefreshHit, Miss, Error. Agrupar por URI y por ese campo te da el diagnóstico en una consulta.

SELECT uri, x_edge_result_type, count(*) AS n
FROM cloudfront_logs
WHERE date >= current_date - interval '7' day
GROUP BY 1, 2
ORDER BY n DESC
LIMIT 50;

Si la misma URI aparece arriba con Miss y mucho volumen, solo hay dos explicaciones: la cache key la está fragmentando, o el origen manda un Cache-Control que impide cachearla. Las dos se arreglan en diez minutos cuando sabes cuál de las dos es.

Lo que haría hoy, en este orden

Decidir el Cache-Control en el origen por tipo de ruta antes que nada. Empezar la cache key vacía y añadir solo lo justificable. Poner hash en el nombre de los assets para que invalidar sea excepcional. stale-while-revalidate en todo lo que sea HTML. Y medir con x-edge-result-type antes de tocar la siguiente palanca.

Un CDN mal configurado no es neutral: añade un salto, una capa donde el bug se esconde y una factura de transferencia que pagas dos veces. Bien configurado es de las pocas cosas en AWS donde el ahorro y la latencia mejoran en la misma dirección. La diferencia entre las dos versiones no es el producto: son tres decisiones que caben en una tarde.

Yohangel Ramos

Escrito por Yohangel Ramos

Senior Fullstack Developer y Tech Lead. Construyo con React, Next.js, Nest.js y AWS — y escribo sobre lo que aprendo en el camino.

Hablemos →

Sigue leyendo

Frontend

INP: por qué tu app se siente lenta aunque cargue rápido

AWS

El NAT Gateway que nadie pidió: la factura silenciosa de tu VPC