Last-Modified es una cabecera de respuesta HTTP que indica la fecha y hora de la última modificación de un recurso en el servidor, permitiendo al cliente realizar solicitudes condicionales mediante If-Modified-Since. Si el recurso no ha cambiado desde la fecha indicada, el servidor devuelve 304 Not Modified sin enviar el cuerpo de la respuesta, lo que ahorra significativamente ancho de banda. Según RFC 7232 (IETF, 2014), las solicitudes condicionales con Last-Modified reducen el tiempo de carga de páginas entre un 30 y un 60% en visitas repetidas. La cabecera es compatible automáticamente con la mayoría de servidores HTTP y proxies.
Puntos Clave
Last-Modified es una cabecera HTTP que pertenece al grupo de cabeceras de solicitudes condicionales. El servidor la añade a una respuesta GET o HEAD, indicando la fecha y hora de la última modificación del recurso solicitado en formato HTTP-date: Last-Modified: Wed, 02 Jul 2025 14:30:00 GMT. El cliente (navegador, aplicación móvil, proxy) guarda esta fecha junto con el recurso en caché. En una solicitud posterior, el cliente envía la cabecera If-Modified-Since con la misma fecha, y el servidor la compara con la hora actual de modificación del recurso.
El protocolo de solicitudes condicionales con Last-Modified está definido en RFC 7232 y es compatible con todos los servidores HTTP modernos. El formato de fecha está estrictamente regulado — solo GMT (Greenwich Mean Time) sin indicación de zona horaria. El servidor debe devolver la fecha en tres formatos posibles: RFC 1123 (estándar), RFC 850 (obsoleto) o ANSI C asctime. En la práctica, casi todos los servidores usan el formato RFC 1123 con una longitud fija de 29 caracteres.
Last-Modified pertenece a la categoría de mecanismos de validación de caché: no le dice al cliente si la respuesta puede almacenarse en caché, sino que proporciona una herramienta para verificar la vigencia de un recurso ya almacenado en caché. La política de almacenamiento en caché se define por separado mediante la cabecera Cache-Control. Según un estudio de Akamai (2025), la configuración adecuada de Last-Modified junto con Cache-Control reduce la carga en los servidores de origen hasta en un 70% para contenido estático.
La cabecera Last-Modified fue definida ya en HTTP/1.0 (RFC 1945, 1996) y se convirtió en uno de los primeros mecanismos de gestión de caché en la web. Antes de la aparición de ETag en HTTP/1.1, era la única forma de realizar solicitudes condicionales. A pesar de su antigüedad, la cabecera sigue siendo relevante por su simplicidad — el servidor no necesita calcular un hash del contenido, solo tiene que leer la marca de tiempo del archivo del sistema de archivos o el campo updated_at de la base de datos.
El ciclo completo consta de tres etapas. En la primera solicitud, el servidor devuelve el recurso con la cabecera Last-Modified y el estado HTTP 200 OK. El cliente almacena en caché la respuesta junto con la fecha. En una solicitud repetida, el cliente envía la cabecera If-Modified-Since con la fecha guardada. El servidor compara esta fecha con la hora actual de modificación del recurso. Si el recurso no ha cambiado — devuelve 304 Not Modified con cuerpo vacío. Si ha cambiado — 200 OK con nuevos datos y un nuevo Last-Modified.
// Primera solicitud — el servidor devuelve el recurso con una fecha
HTTP/1.1 200 OK
Content-Type: application/json
Last-Modified: Wed, 02 Jul 2025 14:30:00 GMT
[{"id": 1, "name": "Alice"}]
// Solicitud repetida — el cliente envía la fecha guardada
GET /api/users HTTP/1.1
Host: example.com
If-Modified-Since: Wed, 02 Jul 2025 14:30:00 GMT
// Respuesta — los datos no han cambiado
HTTP/1.1 304 Not Modified
Para aplicaciones móviles, Last-Modified es especialmente útil para la sincronización de datos. La aplicación guarda la fecha de la última actualización exitosa y la envía al servidor en If-Modified-Since. Si hay más datos o estos han cambiado — el servidor devuelve el conjunto completo. Si no — 304, y la aplicación usa la copia local. OkHttp y URLSession admiten este mecanismo automáticamente mediante sistemas de caché integrados.
Para archivos estáticos, Nginx y Apache toman la fecha de los atributos del sistema de archivos — mtime (hora de modificación). Para contenido dinámico, el código del servidor debe establecer explícitamente Last-Modified según la lógica de negocio: el campo updated_at de la base de datos, la fecha del último commit en Git, la marca de tiempo del artefacto de compilación. Si Last-Modified no se establece explícitamente, el servidor puede no enviar la cabecera y el cliente no podrá realizar solicitudes condicionales por fecha.
Last-Modified y ETag realizan una tarea similar — permitir al cliente verificar la vigencia de la caché — pero tienen diferencias fundamentales. Last-Modified usa una marca de tiempo, ETag usa un identificador único de versión. Cada enfoque tiene sus propios escenarios donde es más efectivo, y la especificación HTTP recomienda usar ambas cabeceras juntas.
| Criterio | Last-Modified | ETag |
|---|---|---|
| Esencia | Fecha de última modificación | Identificador único de versión |
| Precisión | Hasta un segundo | Hasta un bit (hash) |
| Complejidad de implementación | Baja — automática desde el sistema de archivos | Media — requiere cálculo de hash |
| Servidores en clúster | Problema: mtime puede diferir entre nodos | Estable con datos idénticos entre nodos |
| Soporte de rangos | No afecta a solicitudes Range | Requiere ETag fuerte para rangos |
| Recomendación | Para archivos estáticos y APIs simples | Para APIs donde se requiere verificación precisa |
La principal ventaja de Last-Modified es la simplicidad. El servidor no necesita calcular un hash del contenido, lo que ahorra recursos de CPU en cada solicitud. Para proyectos de alto tráfico que sirven archivos estáticos o datos con marcas de tiempo claras, Last-Modified sigue siendo la opción óptima. ETag, por otro lado, proporciona una precisión absoluta — cambiar una sola letra en una respuesta JSON cambiará el ETag, pero puede no cambiar la fecha (si el archivo se sobrescribió con la misma versión).
La especificación recomienda devolver ambas cabeceras simultáneamente. El servidor incluye tanto Last-Modified como ETag en la respuesta 200 OK. El cliente envía ambas cabeceras condicionales — If-Modified-Since e If-None-Match. El servidor verifica primero ETag (tiene prioridad), luego Last-Modified. Si al menos una señala un cambio — se devuelve la respuesta completa. Esto proporciona la máxima flexibilidad: ETag garantiza precisión, Last-Modified proporciona una verificación de respaldo para clientes que no admiten ETag.
La configuración de Last-Modified depende del tipo de servidor. Para Nginx y Apache, Last-Modified se establece automáticamente para archivos estáticos basándose en mtime. Para aplicaciones dinámicas, la cabecera debe configurarse en el código del servidor. Veamos la configuración en plataformas populares.
// Express.js — configurar Last-Modified
app.get("/api/users", async (req, res) => {
const updatedAt = await getLastUpdate()
const ifModifiedSince = req.get("If-Modified-Since")
// Verificar If-Modified-Since
if (ifModifiedSince && new Date(ifModifiedSince)
>= updatedAt) {
return res.status(304).end()
}
const users = await getUsers()
res.set("Last-Modified", updatedAt.toUTCString())
res.json(users)
})
En el ejemplo de Express.js, el servidor obtiene la fecha de la última actualización de datos de la base de datos, verifica If-Modified-Since del cliente y, si la caché sigue vigente, devuelve 304. Si los datos han cambiado — establece un nuevo Last-Modified y devuelve la respuesta completa. toUTCString() convierte la fecha al formato HTTP requerido. En producción, conviene almacenar en caché updatedAt en Redis para evitar consultar la base de datos en cada solicitud.
Nginx establece automáticamente Last-Modified para archivos estáticos según la hora de última modificación del archivo. Se puede deshabilitar o cambiar este comportamiento mediante la directiva etag (deshabilitando ETag) o a través del módulo ngx_http_headers_module. Para solicitudes proxy al backend, Last-Modified se transmite desde la respuesta upstream sin cambios. Importante: si el backend no devuelve Last-Modified, Nginx no lo agregará automáticamente para respuestas dinámicas.
Last-Modified tiene varias limitaciones conocidas. La principal es la precisión de segundo. Si un recurso cambia dos veces dentro de un segundo, el cliente puede perder la nueva versión. En la práctica esto es un escenario raro, pero para actualizaciones de alta frecuencia (flujos de cotizaciones, chats) se recomienda ETag. La segunda limitación es el problema de agrupación en clúster: en diferentes servidores, un archivo puede tener distinto mtime debido a copias o despliegues, lo que hace que Last-Modified sea inconsistente.
La tercera limitación — el manejo de If-Modified-Since con precisión de segundo puede generar solicitudes innecesarias al sondear el servidor con frecuencia. Si el cliente envía If-Modified-Since cada 500 ms, el servidor devuelve 200 OK cada vez porque la fecha no ha cambiado, pero el recurso ya se ha actualizado. La solución es usar una combinación con ETag: ETag detectará el cambio dentro de un segundo, mientras que Last-Modified se mantiene como respaldo.
El cuarto problema — Last-Modified no distingue entre diferentes versiones del mismo recurso con la misma fecha. Si un archivo se restaura desde una copia de seguridad y su mtime coincide con el original, el cliente no notará que el contenido ha cambiado. ETag resuelve este problema: el hash del contenido cambiará garantizadamente ante cualquier modificación de datos, independientemente de la marca de tiempo. Para datos críticos, use siempre ambas cabeceras.
Preguntas Frecuentes
Solo GMT (Greenwich Mean Time) en formato RFC 1123: día de la semana, día, mes, año, horas:minutos:segundos. Ejemplo: Wed, 02 Jul 2025 14:30:00 GMT. La zona horaria es siempre GMT, no se permiten otros formatos.
Técnicamente sí, pero viola RFC 7232. Si el servidor devuelve una fecha futura, los clientes no actualizarán el recurso hasta que llegue esa fecha. Dicha configuración se considera un error — la fecha debe estar en el pasado o presente.
No, las solicitudes condicionales If-Modified-Since solo funcionan con GET y HEAD. Las solicitudes POST no se almacenan en caché ni usan validación por fecha. Para verificaciones de vigencia en POST, use ETag o mecanismos personalizados.
Cache-Control define la política de almacenamiento en caché (tiempo máximo de almacenamiento, quién puede almacenar en caché), mientras que Last-Modified es un mecanismo de validación para caché caducada. Después de que expire el max-age, el cliente envía If-Modified-Since para verificar la vigencia.
Verifique que el servidor realmente esté estableciendo la cabecera desde la fuente correcta — base de datos, sistema de archivos o API. Para respuestas dinámicas, asegúrese de llamar explícitamente a res.setHeader(“Last-Modified”, ...) en el código del manejador.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también