Back to HTTP Headers

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:

  1. Client fetches a resource; server responds with Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
  2. Client caches the response and the timestamp
  3. Later (after cache expires), client sends If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT
  4. Server compares that timestamp to the resource's current modification time:
    • Not modified304 Not Modified (no body, cache still valid)
    • Modified200 OK with new content and new Last-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.