Last-Modified — HTTP-заголовок відповіді, який вказує дату й час останньої зміни ресурсу на сервері, дозволяючи клієнту виконувати умовні запити через If-Modified-Since. Якщо ресурс не змінився з указаної дати, сервер повертає 304 Not Modified без передачі тіла відповіді, що суттіво економить трафік. За даними RFC 7232 (IETF, 2014), умовні запити з Last-Modified скорочують час завантаження сторінок на 30-60% при повторних відвідуваннях. Заголовок автоматично підтримується більшістю HTTP-серверів та проксі.
Головне
Last-Modified — це HTTP-заголовок, що належить до групи заголовків умовних запитів. Сервер додає його до відповіді на 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 |
|---|---|---|
| Суть | Дата останньої зміни | Унікальний ідентифікатор версії |
| Точність | До секунди | До біта (хеш) |
| Складність реалізації | Низька — автоматично від файлової системи | Середня — потрібує обчислення хешу |
| Кластерні сервери | Проблема: mtime може відрізнятися на вузлах | Стабільний при однакових даних на вузлах |
| Підтримка діапазонів | Не впливає на Range запити | Потрібує сильний 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 залишиться як резервний механізм.
Четверта проблема — 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також