Last-Modified caching response
The date and time the server last changed the resource — used for cache validation and conditional requests.
What it does
Last-Modified tells clients when the server last changed the resource. Clients store this value and send it back in future requests via If-Modified-Since. The server compares it against the current modification time: if nothing changed, it returns 304 Not Modified (no body, saves bandwidth); if the resource is newer, it returns 200 OK with the updated content.
It's the older, simpler alternative to ETag for cache validation — easier to implement (just use the file's mtime or the DB row's updated_at) but less precise.
Syntax
Last-Modified: <http-date>
Always in RFC 7231 HTTP date format — GMT, never local timezone:
Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
Last-Modified: Tue, 15 Nov 2022 12:45:26 GMT
How conditional validation works
The full flow:
- Client fetches a resource; server responds with
Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT - Client caches the response and the timestamp
- Later (after cache expires), client sends
If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT - Server compares that timestamp to the resource's current modification time:
- Not modified →
304 Not Modified(no body, cache still valid) - Modified →
200 OKwith new content and newLast-Modified
- Not modified →
The 304 response saves the full body download. The client reuses its cached copy.
The 1-second granularity problem
Last-Modified has a hard limitation: HTTP dates have 1-second resolution. If a resource changes twice within the same second, both changes get the same Last-Modified timestamp. A client that cached the first version and sends If-Modified-Since with that timestamp will get a 304 for the second version — incorrectly serving the stale first version.
This is the core reason ETag exists. ETags are content-based (or version-based) and have no granularity limitation. For resources that update rapidly or where correctness is critical, always use ETag.
In practice, for most content (blog posts, product pages, documentation), 1-second granularity is fine.
Last-Modified vs ETag
Last-Modified |
ETag |
|
|---|---|---|
| Based on | Timestamp | Content hash or version identifier |
| Granularity | 1 second | Exact |
| Easy to implement | Yes (use mtime or updated_at) |
Requires generating and storing a hash/version |
| Works for range requests | Weak (not recommended) | Strong ETags required |
| Clock dependency | Yes (server clock) | No |
| Precedence | Lower | Higher (ETag wins when both present) |
Best practice: send both. Clients use ETag first; Last-Modified is a fallback for caches that don't support ETags. Most web servers and frameworks handle this automatically for static files.
How it interacts with other headers
Last-Modified pairs with If-Modified-Since (on GET requests) and If-Unmodified-Since (on PUT/DELETE requests for concurrency control).
When both ETag and Last-Modified are present and a client sends both If-None-Match and If-Modified-Since, the server should evaluate If-None-Match first — ETags take precedence.
Cache-Control: no-cache combined with Last-Modified means the client always revalidates, but revalidation is cheap — a successful 304 saves the body download.
Generating Last-Modified
For static files: Use the filesystem mtime. Web servers (nginx, Apache, Caddy) do this automatically.
For database-backed content: Use the row's updated_at timestamp. In Laravel:
// On a response:
return response()
->json($article)
->header('Last-Modified', $article->updated_at->toRfc7231String());
For aggregated pages (e.g., a list page): Use the MAX(updated_at) across all records that contribute to the page. The page is modified when any record changes.
Common mistakes and gotchas
Using local server time without converting to GMT. Last-Modified must be GMT. Forgetting the timezone conversion produces invalid dates and breaks validation.
Setting Last-Modified on dynamically generated content. If a page is generated from a database query with no meaningful "last changed" time, don't fabricate a Last-Modified. An incorrect or always-changing timestamp defeats the purpose.
Assuming Last-Modified is sufficient for all cases. For file downloads, rapid-update scenarios, or anything where sub-second accuracy matters, use ETag. Use Last-Modified as the fallback, not the primary mechanism.
Not sending Last-Modified at all. Without either Last-Modified or ETag, caches can't do conditional validation — every cache miss results in a full download. Set at least one.
Real-world examples
Static file response:
HTTP/1.1 200 OK
Content-Type: text/javascript
Cache-Control: public, max-age=86400
Last-Modified: Mon, 10 Jun 2026 08:00:00 GMT
ETag: "d41d8cd98f00b204e9800998ecf8427e"
Conditional GET (cache revalidation):
GET /api/articles/1 HTTP/1.1
If-Modified-Since: Mon, 10 Jun 2026 08:00:00 GMT
If-None-Match: "d41d8cd98f00b204e9800998ecf8427e"
HTTP/1.1 304 Not Modified
Last-Modified: Mon, 10 Jun 2026 08:00:00 GMT
ETag: "d41d8cd98f00b204e9800998ecf8427e"
Cache-Control: public, max-age=86400
Resource has changed:
HTTP/1.1 200 OK
Content-Type: application/json
Last-Modified: Mon, 22 Jun 2026 09:30:00 GMT
ETag: "new-etag-value"
{"id": 1, "title": "Updated Article", ...}
FAQ
Can Last-Modified be in the future?
Technically you can set it to a future date, but you shouldn't. Future Last-Modified values confuse cache freshness calculations and conditional request logic. If your server clock is ahead of a client's, you might accidentally produce future timestamps — another reason to keep server clocks synchronised with NTP.
Does Last-Modified work for POST/PUT responses?
Last-Modified on a POST/PUT response indicates when the resource was last modified after the operation. It's valid and useful for REST APIs — the client immediately has a validator for future conditional requests without needing a separate GET.
What if the resource has never been modified since creation?
Set Last-Modified to the creation time. "Modified" in HTTP encompasses creation — the resource "changed" from non-existent to existing at that time.
Is Last-Modified useful for SEO?
Search engine crawlers use Last-Modified and If-Modified-Since for efficient re-crawling — they check if a page has changed before re-indexing. Setting accurate Last-Modified values helps search engines crawl your site more efficiently and index updates faster.
Fun fact
Last-Modified is one of the oldest headers in HTTP — it appeared in the original HTTP/0.9 and HTTP/1.0 specifications in the early 1990s, when the web was primarily a document distribution system. Back then, files had modification times and servers could report them. The header made complete sense for a document web. As the web shifted toward dynamic applications generating content on the fly, Last-Modified became harder to set correctly — which is part of why ETags were invented as a more flexible alternative in HTTP/1.1.