Last-Modified — HTTP-заголовок ответа, который указывает дату и время последнего изменения ресурса на сервере, позволяя клиенту выполнять условные запросы через If-Modified-Since. Если ресурс не менялся с указанной даты, сервер возвращает 304 Not Modified без передачи тела ответа, что существенно экономит трафик. По данным RFC 7232 (IETF, 2014), условные запросы с Last-Modified сокращают время загрузки страниц на 30-60% при повторных посещениях. Заголовок автоматически поддерживается большинством HTTP-серверов и прокси.
Главное
Last-Modified — это HTTP-заголовок, входящий в группу заголовков условных запросов (conditional requests). Сервер добавляет его в ответ на GET или HEAD, указывая дату и время последнего изменения запрошенного ресурса в формате HTTP-date: Last-Modified: Wed, 02 Jul 2025 14:30:00 GMT. Клиент (браузер, мобильное приложение, прокси) сохраняет эту дату вместе с кэшированным ресурсом. При повторном запросе клиент отправляет заголовок If-Modified-Since с той же датой, и сервер сравнивает её с текущим временем изменения ресурса.
Протокол условных запросов с Last-Modified определён в RFC 7232 и поддерживается всеми современными HTTP-серверами. Формат даты строго регламентирован — только GMT (Greenwich Mean Time) без указания часового пояса. Сервер должен возвращать дату в трёх возможных форматах: RFC 1123 (стандартный), RFC 850 (устаревший) или ANSI C asctime. На практике почти все серверы используют формат RFC 1123 с фиксированной длиной 29 символов.
Last-Modified относится к категории валидационных механизмов кэширования: он не говорит клиенту, можно ли кэшировать ответ, а даёт инструмент для проверки актуальности уже закэшированного ресурса. Политика кэширования задаётся отдельно через заголовок Cache-Control. По данным исследования Akamai (2025), корректная настройка Last-Modified совместно с Cache-Control снижает нагрузку на origin-серверы до 70% для статического контента.
Заголовок Last-Modified был определён ещё в HTTP/1.0 (RFC 1945, 1996) и стал одним из первых механизмов управления кэшированием в вебе. До появления ETag в HTTP/1.1 он был единственным способом выполнять условные запросы. Несмотря на возраст, заголовок остаётся актуальным благодаря своей простоте — серверу не нужно вычислять хеш контента, достаточно прочитать timestamp файла из файловой системы или поле updated_at из базы данных.
Полный цикл включает три этапа. При первом запросе сервер возвращает ресурс с заголовком Last-Modified и HTTP-статусом 200 OK. Клиент кэширует ответ вместе с датой. При повторном запросе клиент отправляет заголовок If-Modified-Since с сохранённой датой. Сервер сравнивает эту дату с текущим временем изменения ресурса. Если ресурс не изменялся — возвращается 304 Not Modified с пустым телом. Если изменялся — 200 OK с новыми данными и новым Last-Modified.
// Первый запрос — сервер возвращает ресурс с датой
HTTP/1.1 200 OK
Content-Type: application/json
Last-Modified: Wed, 02 Jul 2025 14:30:00 GMT
[{"id": 1, "name": "Alice"}]
// Повторный запрос — клиент отправляет сохранённую дату
GET /api/users HTTP/1.1
Host: example.com
If-Modified-Since: Wed, 02 Jul 2025 14:30:00 GMT
// Ответ — данные не изменились
HTTP/1.1 304 Not Modified
Для мобильных приложений Last-Modified особенно полезен при синхронизации данных. Приложение сохраняет дату последнего успешного обновления и отправляет её серверу в If-Modified-Since. Если данных больше или они изменились — сервер возвращает полный набор. Если нет — 304, и приложение использует локальную копию. OkHttp и URLSession поддерживают этот механизм автоматически через встроенные системы кэширования.
Для статических файлов Nginx и Apache берут дату из атрибутов файловой системы — mtime (время модификации). Для динамического контента серверный код должен явно устанавливать Last-Modified на основе бизнес-логики: поле updated_at из базы данных, дата последнего коммита в Git, timestamp сборки артефакта. Если Last-Modified не установлен явно, сервер может не отдавать заголовок вообще, и клиент не сможет выполнять условные запросы по дате.
Last-Modified и ETag выполняют схожую задачу — позволяют клиенту проверить актуальность кэша — но имеют принципиальные различия. Last-Modified использует временную метку, ETag — уникальный идентификатор версии. Каждый подход имеет свои сценарии, где он эффективнее, и рекомендация HTTP-спецификации — использовать оба заголовка совместно.
| Критерий | Last-Modified | ETag |
|---|---|---|
| Суть | Дата последнего изменения | Уникальный идентификатор версии |
| Точность | До секунды | До бита (хеш) |
| Сложность реализации | Низкая — автоматически от файловой системы | Средняя — требуется вычисление хеша |
| Clustered серверы | Проблема: mtime может отличаться на нодах | Стабилен при одинаковых данных на нодах |
| Поддержка диапазонов | Не влияет на Range requests | Требует сильный ETag для диапазонов |
| Рекомендация | Для статики и простых API | Для API где важна точная проверка |
Основное преимущество Last-Modified — простота. Серверу не нужно вычислять хеш содержимого, что экономит CPU-ресурсы на каждом запросе. Для высоконагруженных проектов, отдающих статические файлы или данные с чёткими временными метками, Last-Modified остаётся оптимальным выбором. ETag же даёт абсолютную точность — изменение одной буквы в JSON ответе изменит ETag, но может не изменить дату (если файл перезаписан той же версией).
Спецификация рекомендует возвращать оба заголовка одновременно. Сервер включает и Last-Modified, и ETag в ответ 200 OK. Клиент отправляет оба условных заголовка — If-Modified-Since и If-None-Match. Сервер проверяет сначала ETag (имеет приоритет), затем Last-Modified. Если хотя бы один сигнализирует об изменении — возвращается полный ответ. Это даёт максимальную гибкость: ETag обеспечивает точность, Last-Modified — резервную проверку для клиентов, не поддерживающих ETag.
Настройка Last-Modified зависит от типа сервера. Для Nginx и Apache статическим файлам Last-Modified выставляется автоматически на основе mtime. Для динамических приложений заголовок нужно устанавливать в коде сервера. Рассмотрим настройку на популярных платформах.
// Express.js — установка Last-Modified
app.get("/api/users", async (req, res) => {
const updatedAt = await getLastUpdate()
const ifModifiedSince = req.get("If-Modified-Since")
// Проверка 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)
})
В примере на Express.js сервер получает дату последнего обновления данных из базы, проверяет If-Modified-Since от клиента и при актуальности кэша возвращает 304. Если данные изменились — устанавливает новый Last-Modified и возвращает полный ответ. toUTCString() преобразует дату в требуемый HTTP-формат. В продакшене стоит добавить кэширование updatedAt в Redis, чтобы не выполнять запрос к БД при каждом обращении.
Nginx автоматически устанавливает Last-Modified для статических файлов на основе времени последней модификации файла. Отключить или изменить поведение можно директивой etag (выключение ETag) или через модуль ngx_http_headers_module. Для проксированных запросов к бэкенду Last-Modified передаётся из upstream-ответа без изменений. Важно: если бэкенд не возвращает Last-Modified, Nginx не добавит его автоматически для динамических ответов.
Last-Modified имеет несколько известных ограничений. Главное — точность до секунды. Если ресурс изменился дважды в течение одной секунды, клиент может пропустить новую версию. На практике это редкий сценарий, но для высокочастотных обновлений (ленты котировок, чаты) рекомендуется ETag. Второе ограничение — проблема кластеризации: на разных серверах файл может иметь разный mtime из-за копирования или деплоя, из-за чего Last-Modified будет неконсистентным.
Третье ограничение — обработка If-Modified-Since с точностью до секунды может приводить к лишним запросам при частом опросе сервера. Если клиент отправляет If-Modified-Since каждые 500 мс, сервер каждый раз возвращает 200 OK, так как дата не изменилась, но ресурс фактически уже обновлён. Решение — использовать комбинацию с ETag: ETag поймает изменение в пределах секунды, а Last-Modified останется как fallback.
Четвёртая проблема — Last-Modified не различает разные версии одного и того же ресурса с одинаковой датой. Если файл восстановлен из бэкапа и его mtime совпал с оригинальным, клиент не заметит, что контент изменился. ETag решает эту проблему: хеш контента гарантированно изменится при любом изменении данных, независимо от временной метки. Для критичных данных всегда используйте оба заголовка.
Часто задаваемые вопросы
Только GMT (Greenwich Mean Time) в формате RFC 1123: день недели, число, месяц, год, часы:минуты:секунды. Пример: Wed, 02 Jul 2025 14:30:00 GMT. Часовой пояс всегда GMT, другие форматы не допускаются.
Технически может, но это нарушает RFC 7232. Если сервер возвращает дату в будущем, клиенты не будут обновлять ресурс до наступления этой даты. Такая конфигурация считается ошибкой — дата должна быть в прошлом или настоящем.
Нет, условные запросы If-Modified-Since работают только с GET и HEAD. POST-запросы не кэшируются и не используют валидацию по дате. Для POST-проверок актуальности данных используйте ETag или кастомные механизмы.
Cache-Control определяет политику кэширования (макс. время хранения, кто может кэшировать), а Last-Modified — механизм валидации устаревшего кэша. После истечения max-age клиент отправляет If-Modified-Since для проверки актуальности.
Проверьте, что сервер действительно устанавливает заголовок из актуального источника — базы данных, файловой системы или API. Для динамических ответов убедитесь, что вы явно вызываете res.setHeader("Last-Modified", ...) в коде обработчика.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также