Encabezados HTTP Cache-Control Qué significan realmente no-cache, no-store y max-age
Una explicación práctica de las directivas Cache-Control — qué hacen los navegadores y los CDNs con no-cache, no-store, max-age, s-maxage y ETags. Incluyendo los errores que afectan a la mayoría de los desarrolladores en producción.
Usted probablemente ha escrito Cache-Control: no-cache y asumido que el navegador omitiría completamente el caché. No lo hará. no-cache significa "revalidar antes de servir desde el caché". Si realmente desea que la respuesta nunca se almacene, eso es no-store. Esta confusión causa errores de datos obsoletos en producción que son difíciles de diagnosticar porque todo parece bien en la pestaña de Redes en la primera carga.
Vamos a revisar cada directiva claramente — qué hacen los navegadores con ella, qué hacen los CDNs con ella, y los errores que surgen al mezclarlas.
Referencia completa de las directivas Cache-Control
| Directiva | Comportamiento del navegador | Comportamiento del CDN / proxy | Uso típico |
|---|---|---|---|
max-age=N | Almacenar durante N segundos | Almacenar durante N segundos (a menos que s-maxage sobrescriba) | Recursos estáticos, respuestas de API |
s-maxage=N | Ignorado | Almacenar durante N segundos | TTL del CDN separado del navegador |
no-cache | Almacenar pero revalidar cada solicitud | Almacenar pero revalidar cada solicitud | Contenido que cambia frecuentemente con ETags |
no-store | No almacenar en ningún lugar | No almacenar en ningún lugar | Respuestas de autenticación, datos sensibles del usuario |
must-revalidate | Sin entrega obsoleta — revalidar o fallar | Sin entrega obsoleta — revalidar o fallar | Respuestas de API donde la obsolescencia significa que el contenido es incorrecto |
proxy-revalidate | Ignorado | Sin entrega obsoleta en cachés compartidos | Debe revalidar en CDNs específicos |
private | El navegador puede cachearlo | No debe cachearse | Páginas específicas del usuario |
public | Cualquier caché puede almacenarla | Puede cachearla | Recursos estáticos compartidos |
immutable | Nunca revalidar dentro del max-age | Varía según el CDN | Recursos hashados o versionados |
stale-while-revalidate=N | Servir contenido obsoleto durante N segundos mientras se obtiene el fresco | Varía según el CDN | Velocidad sin obsolescencia dura |
max-age y s-maxage: TTL del navegador vs CDN
max-age=N indica cuántos segundos la respuesta es fresca. Después de N segundos, la respuesta almacenada se vuelve obsoleta y debe revalidarse antes de su uso.
s-maxage=N es exclusiva para CDNs. Los navegadores la ignoran completamente. Si desea que un CDN almacene durante una hora pero el navegador solo durante 5 minutos:
Cache-Control: max-age=300, s-maxage=3600
El navegador almacena durante 5 minutos. CloudFront, Fastly, Nginx — ellos usarán 3600 segundos. Una trampa común: establecer max-age=0 creyendo que desactiva el caché. No lo hace. max-age=0 significa que la respuesta se vuelve inmediatamente obsoleta — el navegador aún la almacena y la revalida, probablemente recibiendo un 304. Si nunca desea que se almacene, use no-store.
no-cache: No lo que piensas
Cache-Control: no-cache no significa "no usar el caché". Significa "no servir desde el caché sin revalidar primero con el servidor".
La secuencia cuando el navegador tiene una respuesta almacenada con no-cache:
- una nueva solicitud llega para el recurso almacenado
- El navegador envía el ETag de la respuesta almacenada (a través de
If-None-Match) o la marca de última modificación al servidor - Si el contenido no ha cambiado → el servidor devuelve
304 Not Modifiedsin cuerpo - Si el contenido ha cambiado → el servidor devuelve
200 OKcon la nueva respuesta
La ventaja sobre no-store: aún obtienes ahorro de ancho de banda de respuestas 304. Si tu contenido cambia ocasionalmente pero debe ser fresco cuando cambia, no-cache combinado con un ETag es la combinación adecuada.
no-store: El verdadero "No almacene esto"
Cache-Control: no-store significa que la respuesta no debe almacenarse en ningún lugar — ni en el caché del navegador, ni en un CDN, ni en un proxy intermedio. Ninguna copia, punto final.
Usa esto para:
- Respuestas de autenticación (tokens de inicio de sesión, datos de sesión)
- Datos personales sensibles
- Contenido de una sola vez (confirmaciones de pago, páginas de OTP)
Una sutileza: no-store no impide que una página aparezca en el caché de navegación hacia atrás (bfcache). Los navegadores mantienen una copia en memoria para el rendimiento de navegación que es independiente del caché HTTP. Si necesitas manejar problemas con el botón atrás después de cerrar sesión, conecta al evento pageshow y verifica event.persisted.
must-revalidate: Sin gracia obsoleta
Los especificaciones de caché HTTP permiten que los cachés sirvan respuestas obsoletas cuando el origen no está disponible — una característica de resiliencia que la mayoría de los desarrolladores no conocen. must-revalidate elimina esa flexibilidad: una vez que una respuesta almacenada se vuelve obsoleta, el caché debe revalidar o devolver un 504. No se sirve contenido obsoleto en ningún caso.
# Without must-revalidate: CDN may serve stale if origin is slow or down
Cache-Control: max-age=3600
# With must-revalidate: stale = error, not a fallback
Cache-Control: max-age=3600, must-revalidate
Usa esto para respuestas de API donde servir datos obsoletos rompe la funcionalidad — contadores de inventario, precios, estado de autenticación — en lugar de simplemente que se vea un poco mal.
private vs public: El error del CDN que revela datos del usuario
private significa que la respuesta está destinada a un usuario específico. Los navegadores pueden cachearla; los cachés compartidos (CDNs, proxies inversos) no deben hacerlo.
public permite que cualquier caché — incluyendo los compartidos — almacene la respuesta. Algunos cachés solo almacenan respuestas de solicitudes autenticadas si se marca explícitamente public.
El error real que causa esto: un desarrollador copia y pega Cache-Control: public, max-age=3600 de un recurso estático a una página que incluye datos específicos del usuario. El CDN almacena la respuesta. El usuario B hace la misma solicitud y obtiene la página de Usuario A desde el caché. Esto no es teórico — GitHub tuvo una variante de esto en 2018. Marca explícitamente las respuestas autenticadas o específicas del usuario private aunque creas que tu CDN "sabe" que no debe almacenarlas.
ETags y solicitudes condicionales
Los ETags son cómo el servidor dice "aquí está una huella de esta respuesta". El navegador almacena el ETag junto con la respuesta almacenada y lo envía de vuelta en la siguiente solicitud a través de If-None-Match. Si el contenido no ha cambiado, el servidor devuelve 304 Not Modified sin cuerpo — misma enfoque de frescura que no-cache, mucho menos ancho de banda.
El no-cache + Flujo de ETag:
→ GET /api/config HTTP/1.1
← HTTP/1.1 200 OK
Cache-Control: no-cache
ETag: "abc123"
[full response body]
→ GET /api/config HTTP/1.1
If-None-Match: "abc123"
← HTTP/1.1 304 Not Modified
[no body — browser uses its cached copy]
Dos tipos de ETag:
- ETags fuertes (
"abc123") — idénticos byte a byte. Requeridos para el soporte de solicitudes de rango en CDNs. - ETags débiles (
W/"abc123") — semanticamente equivalentes pero no necesariamente idénticos en bytes. Adecuados para la revalidación en el navegador, no para solicitudes de rango.
Nginx genera ETags automáticamente a partir de la hora de modificación del archivo y su tamaño. Express no los agrega por defecto — usa el middleware app.set('etag', 'strong') o el etag explicitamente.
Last-Modified y If-Modified-Since
Concepto similar a ETags pero más grueso — basado en timestamp en lugar de en hash de contenido. El servidor incluye Last-Modified; el navegador envía If-Modified-Since en las solicitudes subsiguientes.
El problema: si reemplazas y las horas de modificación del archivo se actualizan aunque el contenido no haya cambiado, los cachés se invalidan innecesariamente. Un ETag basado en hash de contenido no tendrá este problema. Usa ETags cuando sea posible y trata a Last-Modified como un respaldo para servidores que no soporten ETags.
Vary: El encabezado que multiplica silenciosamente tu caché
El Vary encabezado indica que la respuesta puede diferir según otros encabezados de solicitud. Cada combinación única de esos encabezados obtiene su propia entrada en el caché.
Vary: Accept-Encoding
Esto indica que los cachés deben almacenar respuestas separadas para compresión gzip, brotli y identidad. Correcto y común. El peligroso uno: Vary: Cookie. Cada usuario tiene un conjunto único de cookies, por lo que cada usuario obtiene su propia entrada en el caché — efectivamente desactiva el caché compartido. Muchos frameworks agregan Vary: Cookie silenciosamente. Si tu tasa de aciertos en el caché del CDN es inexplicably baja a pesar de valores generosos de max-age , revisa tus encabezados de respuesta por Vary: Cookie que estén apareciendo desde el middleware de sesión.
Vary: * significa "no cachear esto en realidad" — cada solicitud se trata como única. Es equivalente a no-store para CDNs.
Forzar actualizaciones con parámetros de consulta
Cuando necesitas forzar descargas frescas de recursos versionados, agregar un parámetro de consulta es el enfoque estándar — la cadena de consulta forma parte de la URL, por lo que se trata como un recurso nuevo tanto para navegadores como para CDNs:
/app.js?v=2.1.4
/styles.css?hash=a1b2c3d4e5f6
Si estás construyendo parámetros de caché que se generan dinámicamente a partir de hashes o cadenas de versión que pueden contener caracteres especiales, asegúrate de codificarlos antes de agregarlos. El Codificador/Decodificador de URL lo maneja rápidamente si estás probando o construyendo URLs manualmente.
Los tres errores que atacan a la mayoría de los desarrolladores
1. Usar no-cache cuando se quiere no-store. Si estás manejando respuestas de autenticación, puntos de salida de sesión o cualquier contenido con datos personales sensibles, necesitas no-store. no-cache deja los datos en el caché del navegador (solo marcados como obsoletos); no-store elimina completamente el rastro. La diferencia importa cuando los usuarios comparten dispositivos.
2. No establecer s-maxage para el control del CDN. Pega el contenido de tu .github/workflows/*.yml aquí s-maxage, tu CDN usa max-age. Si max-age es corto para la frescura del navegador (por ejemplo, 60 segundos), tu CDN almacena durante 60 segundos también — lo cual probablemente no sea lo que deseas. Separa explícitamente los dos TTLs.
3. public en endpoints que devuelven datos del usuario. Este es el incidente de seguridad, no solo un error de rendimiento. Cualquier respuesta que sea personalizada o autenticada debe ser private. Por defecto a private y opta por public solo para recursos verdaderamente compartidos.
Reunir todo lo anterior
El modelo mental: no-cache se refiere a la enfoque de frescura — la respuesta vive en el caché, pero necesita una validación del servidor antes de su uso. no-store se refiere a dejar sin rastro. max-age es tu TTL del navegador. s-maxage es tu TTL del CDN. Los ETags son lo que hacen que la revalidación sea barata.
Asegúrate de tener la private/public diferenciación correcta en cualquier endpoint que toque datos del usuario. Ese único error — copiar un encabezado de caché de un recurso estático a un endpoint autenticado — es el que se convierte en una fuga de datos entre usuarios cuando el CDN comienza a almacenar respuestas personalizadas.
Instalar extensiones
Agregue herramientas IO a su navegador favorito para obtener acceso instantáneo y búsquedas más rápidas
恵 ¡El marcador ha llegado!
Marcador es una forma divertida de llevar un registro de tus juegos, todos los datos se almacenan en tu navegador. ¡Próximamente habrá más funciones!
Herramientas clave
Ver todo Los recién llegados
Ver todoActualizar: Nuestro última herramienta fue agregado el 19 de junio de 2026
