Quién está escuchando esta cabecera
Una respuesta atraviesa varias cachés: el navegador del visitante (privada — sirve a una sola persona), el edge de la CDN (compartida — sirve a todo el mundo) y, a veces, un proxy intermedio. Cache-Control es la forma en que el origen habla con todos ellos a la vez, y por eso existen directivas para dirigirse a cada uno por separado.
Esa distinción condiciona todo lo demás. Un panel de usuario autenticado puede ser cacheable en el navegador de ese usuario y no debe cachearse jamás en el edge, donde otro usuario podría recibirlo. Una página pública de marketing es lo contrario: cachéala con fuerza en el edge y brevemente en el navegador. Decidir primero "privada o compartida" hace obvias todas las demás decisiones.
Las directivas que de verdad importan
max-age=N — reutilizable durante N segundos sin volver a preguntar. El dial principal.
s-maxage=N — lo mismo, pero solo para cachés compartidas (la CDN). Cuando está presente, el edge la obedece e ignora max-age, así que puedes mantener algo una hora en el edge mientras los navegadores lo conservan un minuto.
public / private — public permite que cualquier caché lo almacene; private restringe el almacenamiento al navegador individual. Todo lo específico de un usuario debe ser private.
no-cache — guárdalo, pero revalida con el origen antes de cada reutilización. Es barato cuando no ha cambiado: el servidor puede responder 304 Not Modified sin cuerpo.
no-store — no lo escribas nunca en ninguna caché, ni en memoria ni en disco. Resérvalo para respuestas realmente sensibles; no significa "por favor, que esté fresco", significa "no guardes ninguna copia".
immutable — este cuerpo no cambiará nunca en esta URL, así que ni siquiera revalides al recargar. Solo es cierto para nombres de archivo versionados.
stale-while-revalidate=N — tras la expiración, sigue sirviendo la copia obsoleta hasta N segundos mientras se trae una fresca en segundo plano. El visitante nunca espera a esa recarga.
Tres recetas que cubren casi cualquier sitio
Assets estáticos versionados — el nombre del archivo cambia siempre que cambia el contenido, así que la URL se puede cachear sin riesgo para siempre. Esta es la mayor victoria de caché disponible, y la más segura.
Páginas HTML — la URL se mantiene mientras el contenido cambia, así que necesita una vida corta. Un max-age breve más stale-while-revalidate te da velocidad sin servir la página de ayer.
Respuestas privadas o personalizadas — paneles, carritos, cualquier cosa detrás de un login. Mantenlas fuera de las cachés compartidas; el navegador puede conservarlas brevemente si eso es seguro en tu caso.
Puntos de partida para copiar y pegar — ajusta los números a tu ritmo de releases
# versioned assets: /js/app.a1b2c3.js
Cache-Control: public, max-age=31536000, immutable
# HTML pages (short at the browser, longer at the edge, no waiting on refresh)
Cache-Control: public, max-age=60, s-maxage=600, stale-while-revalidate=86400
# per-user pages: never at the edge
Cache-Control: private, no-store
# an API response that changes often but can lag a little
Cache-Control: public, max-age=0, s-maxage=30, stale-while-revalidate=60
no-cache frente a no-store: el error que conviene evitar
Se leen como sinónimos y no se parecen en nada. no-cache permite almacenar pero exige revalidar antes de reutilizar — la copia se queda y, cuando sigue vigente, el servidor responde 304 sin cuerpo, lo cual es muy barato. no-store prohíbe conservar la respuesta en ningún sitio.
Recurrir a no-store cuando lo que quieres decir es no-cache tira por la borda toda optimización sin ningún beneficio: cada petición se convierte en una descarga completa aunque no haya cambiado nada. Usa no-store solo cuando el problema sea precisamente que exista una copia almacenada — extractos bancarios, páginas de restablecimiento de contraseña, cualquier cosa que no deba quedarse en la caché de una máquina compartida. Para "mostrar siempre lo último", no-cache es la respuesta correcta y mucho más barata.
Cómo interactúa esto con tu CDN
Las cabeceras que envía tu origen son las que el edge obedece cuando decide cuánto tiempo conservar una copia — lo que convierte a Cache-Control en la superficie de control de toda tu cadena de entrega, no en un detalle del navegador. Envía s-maxage y el edge lo seguirá; envía no-store y el edge se negará a cachear en absoluto, así que cada petición viajará hasta tu origen y habrás apagado la CDN de hecho para esa respuesta.
En cdn.com.tr también puedes configurar el comportamiento de caché por regla de entrega desde el panel cuando no puedas cambiar las cabeceras de la aplicación — útil para aplicaciones heredadas que prefieres no tocar. Y cuando cambies lo que devuelve una URL cacheada, recuerda que el edge sigue conservando la copia anterior hasta que caduque: para eso está la purga.
Comprobar lo que envías de verdad
Las suposiciones sobre las cabeceras fallan a menudo — un framework, un plugin o el valor por defecto del servidor web suelen sobrescribir lo que crees haber configurado. Verifícalo con un comando por tipo de URL, y revisa tanto un asset versionado como una página HTML, ya que deberían tener un aspecto completamente distinto. Vigila también un Set-Cookie en una respuesta que pretendías cachear públicamente: muchas cachés se niegan a almacenarlas, y es un motivo habitual de que una página misteriosamente nunca se cachee.
Lee las cabeceras de respuesta reales
# see what the edge and origin actually say
curl -sI https://example.com/ | grep -i "cache-control\|age\|set-cookie"
# compare a versioned asset (should be a long max-age)
curl -sI https://example.com/js/app.a1b2c3.js | grep -i cache-control
Preguntas frecuentes
¿Cuál es un buen max-age para páginas HTML?
Corto — de segundos a unos pocos minutos — porque la URL se mantiene mientras el contenido cambia. Combínalo con un s-maxage más largo en el edge y con stale-while-revalidate para que los visitantes reciban una respuesta instantánea mientras la actualización ocurre en segundo plano.
¿Sigue haciendo falta Expires además de Cache-Control?
No. Cache-Control sustituye a Expires y prevalece allí donde aparecen ambos. Expires solo importa para clientes extremadamente antiguos; enviarlo también es inofensivo, pero no aporta nada.
¿Por qué mi página no se cachea aunque tenga un max-age largo?
Lo más habitual es una cabecera Set-Cookie en la respuesta, una directiva private o no-store en algún punto de la cadena, o una cadena de consulta que convierte cada petición en una clave de caché única. Inspecciona las cabeceras de respuesta reales antes de cambiar la configuración.
¿immutable significa realmente para siempre?
Significa "el cuerpo de esta URL no va a cambiar", así que las cachés se saltan la revalidación por completo. Eso solo es cierto para nombres de archivo versionados. Poner immutable en una URL que luego sobrescribes es la forma de que los visitantes se queden atascados en un archivo antiguo sin manera limpia de arreglarlo.