Last-Modified — nagłówek odpowiedzi HTTP, który wskazuje datę i czas ostatniej modyfikacji zasobu na serwerze, umożliwiając klientowi wykonywanie żądań warunkowych przez If-Modified-Since. Jeśli zasób nie zmienił się od podanej daty, serwer zwraca 304 Not Modified bez przesyłania treści odpowiedzi, co znacząco oszczędza przepustowość. Według RFC 7232 (IETF, 2014), żądania warunkowe z Last-Modified skracają czas ładowania stron o 30–60% przy ponownych odwiedzinach. Nagłówek jest automatycznie obsługiwany przez większość serwerów HTTP i proxy.
Najważniejsze
Last-Modified — to nagłówek HTTP należący do grupy nagłówków żądań warunkowych (conditional requests). Serwer dodaje go do odpowiedzi na GET lub HEAD, wskazując datę i czas ostatniej modyfikacji żądanego zasobu w formacie HTTP-date: Last-Modified: Wed, 02 Jul 2025 14:30:00 GMT. Klient (przeglądarka, aplikacja mobilna, proxy) zapisuje tę datę wraz z buforowanym zasobem. Przy ponownym żądaniu klient wysyła nagłówek If-Modified-Since z tą samą datą, a serwer porównuje ją z bieżącym czasem modyfikacji zasobu.
Protokół żądań warunkowych z Last-Modified jest zdefiniowany w RFC 7232 i obsługiwany przez wszystkie nowoczesne serwery HTTP. Format daty jest ściśle określony — tylko GMT (Greenwich Mean Time) bez wskazywania strefy czasowej. Serwer może zwracać datę w trzech możliwych formatach: RFC 1123 (standardowy), RFC 850 (przestarzały) lub ANSI C asctime. W praktyce prawie wszystkie serwery używają formatu RFC 1123 o stałej długości 29 znaków.
Last-Modified należy do kategorii mechanizmów walidacyjnych buforowania: nie mówi klientowi, czy można buforować odpowiedź, ale daje narzędzie do sprawdzania aktualności już zbuforowanego zasobu. Polityka buforowania jest określana osobno przez nagłówek Cache-Control. Według badań Akamai (2025), prawidłowa konfiguracja Last-Modified wraz z Cache-Control zmniejsza obciążenie serwerów origin aż do 70% dla treści statycznych.
Nagłówek Last-Modified został zdefiniowany już w HTTP/1.0 (RFC 1945, 1996) i był jednym z pierwszych mechanizmów zarządzania buforowaniem w sieci. Przed pojawieniem się ETag w HTTP/1.1 był to jedyny sposób wykonywania żądań warunkowych. Pomimo wieku nagłówek pozostaje aktualny dzięki swojej prostocie — serwer nie musi obliczać skrótu treści, wystarczy odczytać timestamp pliku z systemu plików lub pole updated_at z bazy danych.
Pełny cykl obejmuje trzy etapy. Przy pierwszym żądaniu serwer zwraca zasób z nagłówkiem Last-Modified i statusem HTTP 200 OK. Klient buforuje odpowiedź wraz z datą. Przy ponownym żądaniu klient wysyła nagłówek If-Modified-Since z zapisaną datą. Serwer porównuje tę datę z bieżącym czasem modyfikacji zasobu. Jeśli zasób nie został zmodyfikowany — zwracane jest 304 Not Modified z pustą treścią. Jeśli został zmodyfikowany — 200 OK z nowymi danymi i nowym Last-Modified.
// Pierwsze żądanie — serwer zwraca zasób z datą
HTTP/1.1 200 OK
Content-Type: application/json
Last-Modified: Wed, 02 Jul 2025 14:30:00 GMT
[{"id": 1, "name": "Alice"}]
// Powtórne żądanie — klient wysyła zapisaną datę
GET /api/users HTTP/1.1
Host: example.com
If-Modified-Since: Wed, 02 Jul 2025 14:30:00 GMT
// Odpowiedź — dane nie uległy zmianie
HTTP/1.1 304 Not Modified
Dla aplikacji mobilnych Last-Modified jest szczególnie przydatny przy synchronizacji danych. Aplikacja zapisuje datę ostatniej udanej aktualizacji i wysyła ją serwerowi w If-Modified-Since. Jeśli danych jest więcej lub uległy zmianie — serwer zwraca pełny zestaw. Jeśli nie — 304, a aplikacja używa lokalnej kopii. OkHttp i URLSession obsługują ten mechanizm automatycznie przez wbudowane systemy buforowania.
Dla plików statycznych Nginx i Apache pobierają datę z atrybutów systemu plików — mtime (czas modyfikacji). Dla treści dynamicznych kod serwera musi jawnie ustawiać Last-Modified na podstawie logiki biznesowej: pole updated_at z bazy danych, data ostatniego commita w Git, timestamp budowy artefaktu. Jeśli Last-Modified nie jest jawnie ustawiony, serwer może w ogóle nie zwracać nagłówka, a klient nie będzie mógł wykonywać żądań warunkowych według daty.
Last-Modified i ETag pełnią podobne zadanie — umożliwiają klientowi sprawdzenie aktualności bufora — ale mają zasadnicze różnice. Last-Modified używa znacznika czasu, ETag — unikalnego identyfikatora wersji. Każde podejście ma swoje scenariusze, w których jest skuteczniejsze, a zalecenie specyfikacji HTTP to używanie obu nagłówków razem.
| Kryterium | Last-Modified | ETag |
|---|---|---|
| Istota | Data ostatniej modyfikacji | Unikalny identyfikator wersji |
| Dokładność | Do sekundy | Do bitu (skrót) |
| Złożoność implementacji | Niska — automatycznie z systemu plików | Średnia — wymaga obliczania skrótu |
| Serwery klastrowe | Problem: mtime może się różnić na węzłach | Stabilny przy identycznych danych na węzłach |
| Obsługa zakresów | Nie wpływa na Range requests | Wymaga silnego ETag dla zakresów |
| Zalecenie | Dla statyki i prostych API | Dla API gdzie ważna jest dokładna weryfikacja |
Główną zaletą Last-Modified jest prostota. Serwer nie musi obliczać skrótu zawartości, co oszczędza zasoby CPU przy każdym żądaniu. Dla projektów o wysokim obciążeniu, które dostarczają pliki statyczne lub dane z wyraźnymi znacznikami czasu, Last-Modified pozostaje optymalnym wyborem. ETag natomiast daje absolutną dokładność — zmiana jednej litery w odpowiedzi JSON zmieni ETag, ale może nie zmienić daty (jeśli plik został nadpisany tą samą wersją).
Specyfikacja zaleca zwracanie obu nagłówków jednocześnie. Serwer dołącza zarówno Last-Modified, jak i ETag w odpowiedzi 200 OK. Klient wysyła oba nagłówki warunkowe — If-Modified-Since i If-None-Match. Serwer sprawdza najpierw ETag (ma priorytet), potem Last-Modified. Jeśli przynajmniej jeden sygnalizuje zmianę — zwracana jest pełna odpowiedź. Daje to maksymalną elastyczność: ETag zapewnia dokładność, Last-Modified — zapasowe sprawdzenie dla klientów nieobsługujących ETag.
Konfiguracja Last-Modified zależy od typu serwera. Dla Nginx i Apache plikom statycznym Last-Modified jest ustawiany automatycznie na podstawie mtime. Dla aplikacji dynamicznych nagłówek trzeba ustawić w kodzie serwera. Przyjrzyjmy się konfiguracji na popularnych platformach.
// Express.js — ustawianie Last-Modified
app.get("/api/users", async (req, res) => {
const updatedAt = await getLastUpdate()
const ifModifiedSince = req.get("If-Modified-Since")
// Sprawdzanie 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)
})
W przykładzie na Express.js serwer pobiera datę ostatniej aktualizacji danych z bazy, sprawdza If-Modified-Since od klienta i przy aktualności bufora zwraca 304. Jeśli dane się zmieniły — ustawia nowy Last-Modified i zwraca pełną odpowiedź. toUTCString() przekształca datę na wymagany format HTTP. W środowisku produkcyjnym warto dodać buforowanie updatedAt w Redis, aby nie wykonywać zapytania do bazy przy każdym dostępie.
Nginx automatycznie ustawia Last-Modified dla plików statycznych na podstawie czasu ostatniej modyfikacji pliku. Wyłączyć lub zmienić zachowanie można dyrektywą etag (wyłączenie ETag) lub przez moduł ngx_http_headers_module. Dla żądań proxy do backendu Last-Modified jest przekazywany z odpowiedzi upstream bez zmian. Ważne: jeśli backend nie zwraca Last-Modified, Nginx nie doda go automatycznie dla odpowiedzi dynamicznych.
Last-Modified ma kilka znanych ograniczeń. Głównym jest dokładność do sekundy. Jeśli zasób zmienił się dwukrotnie w ciągu jednej sekundy, klient może przegapić nową wersję. W praktyce jest to rzadki scenariusz, ale w przypadku częstych aktualizacji (kanały notowań, czaty) zaleca się ETag. Drugie ograniczenie to problem klasteryzacji: na różnych serwerach plik może mieć różny mtime z powodu kopiowania lub wdrożenia, przez co Last-Modified będzie niespójny.
Trzecie ograniczenie — obsługa If-Modified-Since z dokładnością do sekundy może prowadzić do zbędnych żądań przy częstym odpytywaniu serwera. Jeśli klient wysyła If-Modified-Since co 500 ms, serwer za każdym razem zwraca 200 OK, ponieważ data się nie zmieniła, ale zasób faktycznie został już zaktualizowany. Rozwiązaniem jest użycie kombinacji z ETag: ETag wykryje zmianę w ciągu sekundy, a Last-Modified pozostanie jako rezerwa.
Czwarty problem — Last-Modified nie rozróżnia różnych wersji tego samego zasobu z tą samą datą. Jeśli plik został przywrócony z kopii zapasowej i jego mtime zgadza się z oryginalnym, klient nie zauważy, że treść się zmieniła. ETag rozwiązuje ten problem: skrót treści gwarantowanie zmieni się przy każdej zmianie danych, niezależnie od znacznika czasu. W przypadku danych krytycznych zawsze używaj obu nagłówków.
Często zadawane pytania
Tylko GMT (Greenwich Mean Time) w formacie RFC 1123: dzień tygodnia, data, miesiąc, rok, godziny:minuty:sekundy. Przykład: Wed, 02 Jul 2025 14:30:00 GMT. Strefa czasowa to zawsze GMT, inne formaty nie są dozwolone.
Technicznie może, ale narusza to RFC 7232. Jeśli serwer zwraca datę w przyszłości, klienci nie będą aktualizować zasobu do momentu nadejścia tej daty. Taka konfiguracja jest uważana za błąd — data powinna być w przeszłości lub teraźniejszości.
Nie, żądania warunkowe If-Modified-Since działają tylko z GET i HEAD. Żądania POST nie są buforowane i nie używają walidacji według daty. Do sprawdzania aktualności danych dla POST używaj ETag lub własnych mechanizmów.
Cache-Control określa politykę buforowania (maksymalny czas przechowywania, kto może buforować), a Last-Modified to mechanizm walidacji nieaktualnego bufora. Po wygaśnięciu max-age klient wysyła If-Modified-Since w celu sprawdzenia aktualności.
Sprawdź, czy serwer rzeczywiście ustawia nagłówek z aktualnego źródła — bazy danych, systemu plików lub API. Dla odpowiedzi dynamicznych upewnij się, że jawnie wywołujesz res.setHeader(„Last-Modified”, ...) w kodzie procedury obsługi.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również