Artículo actualizado en mayo de 2026. La versión original, publicada en el año 2000, cubría exclusivamente las respuestas de ASP clásico. Esta revisión aborda el mecanismo de caché HTTP en su conjunto y muestra cómo controlarlo desde PHP, Node.js, Apache y nginx.
Cómo funciona la caché del navegador
Cuando visitas una página por primera vez, el navegador descarga todos sus recursos: HTML, CSS, imágenes, scripts. La segunda vez, antes de volver a pedirlos al servidor, comprueba si los tiene guardados en local y si siguen siendo válidos. Ese mecanismo es la caché del navegador, y sirve para acelerar la navegación y reducir el ancho de banda consumido.
El problema aparece con las páginas dinámicas: si el servidor genera HTML diferente en cada petición (datos en tiempo real, paneles de administración, resultados personalizados), la caché puede mostrar una versión antigua sin que el usuario lo sepa. Evitarlo requiere enviar las cabeceras HTTP correctas.
Las cabeceras que controlan la caché
La cabecera principal es Cache-Control, definida en el RFC 7234. Sus directivas más importantes para desactivar la caché son:
Directiva | Efecto |
| El navegador no guarda nada. Cada visita descarga el recurso completo desde el servidor. Es la opción más agresiva. |
| El navegador guarda el recurso pero siempre consulta al servidor antes de usarlo (mediante ETag o Last-Modified). Solo lo sirve desde caché si el servidor confirma que no ha cambiado. |
| Si la copia en caché ha expirado, el navegador debe pedir confirmación al servidor antes de usarla, sin excepciones. |
| El recurso puede guardarse en el navegador del usuario pero no en proxies ni CDNs intermedios. |
| La copia en caché expira inmediatamente. Combinado con |
La combinación habitual para páginas dinámicas que no deben cachearse en absoluto es:
Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Expires: 0
Pragma: no-cache y Expires: 0 son cabeceras heredadas de HTTP/1.0 que se incluyen por compatibilidad con proxies antiguos.
Implementación en PHP
<?php
// Evitar cualquier caché en páginas dinámicas sensibles
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Cache-Control: post-check=0, pre-check=0', false); // IE legacy
header('Pragma: no-cache');
header('Expires: 0');
?>
Estas cabeceras deben enviarse antes de cualquier salida HTML. Si usas un framework como Laravel o Symfony, ambos tienen middleware o helpers específicos para ello:
// Laravel: en el controlador o en una ruta
return response()->view('mi-vista')
->header('Cache-Control', 'no-store, no-cache, must-revalidate')
->header('Pragma', 'no-cache')
->header('Expires', '0');
Implementación en Node.js (Express)
// Middleware global para rutas dinámicas
app.use((req, res, next) => {
res.set('Cache-Control', 'no-store, no-cache, must-revalidate');
res.set('Pragma', 'no-cache');
res.set('Expires', '0');
next();
});
// O en una ruta concreta
app.get('/dashboard', (req, res) => {
res.set('Cache-Control', 'no-store');
res.json({ data: obtenerDatosEnTiempoReal() });
});
Configuración en Apache (.htaccess)
<IfModule mod_headers.c>
# Sin caché para páginas PHP dinámicas
<FilesMatch ".(php)$">
Header set Cache-Control "no-store, no-cache, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "0"
</FilesMatch>
</IfModule>
Configuración en nginx
location ~ .php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
# Sin caché
add_header Cache-Control "no-store, no-cache, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
Caché inteligente con ETag: el enfoque recomendado para APIs
Para APIs o recursos que cambian con poca frecuencia, el enfoque más eficiente no es eliminar la caché sino validarla con ETags. El servidor asigna una huella digital al recurso; el navegador la guarda y la envía en la siguiente petición. Si la huella coincide, el servidor responde con un 304 Not Modified sin cuerpo, ahorrando ancho de banda. Si ha cambiado, devuelve el recurso actualizado con un nuevo ETag.
<?php
$datos = obtenerDatos();
$etag = md5(serialize($datos));
$etagCliente = $_SERVER['HTTP_IF_NONE_MATCH'] ?? '';
header('ETag: "' . $etag . '"');
header('Cache-Control: no-cache'); // valida siempre, pero puede usar caché si ETag coincide
if ($etagCliente === '"' . $etag . '"') {
http_response_code(304);
exit;
}
header('Content-Type: application/json');
echo json_encode($datos);
Resumen de estrategias
Caso de uso | Cabecera recomendada |
Panel de admin, datos en tiempo real |
|
Página dinámica, datos que cambian poco |
|
Página pública estática (HTML) |
|
Assets con hash en el nombre (JS, CSS) |
|
Si usas PHP y buscas una solución de caché de aplicación más completa para reducir consultas a la base de datos, no para controlar la caché del navegador, el artículo sobre Phpfastcache explica cómo implementar caché de datos en capas con distintos backends.
Imagen: Pexels / Brett Sayles
