Last-Modified: timestamps as a weak validator
Last-Modified states when the server believes the representation last changed. Clients send it back in If-Modified-Since to ask whether anything has changed since.
At a glance
- Direction
- Response header. Clients echo the value in If-Modified-Since or If-Unmodified-Since.
- Defined in
- RFC 9110
- When absent
- A cache with no ETag either revalidates on every request or, with no validator at all, must refetch the whole body.
- Format
- An HTTP date in GMT, for example Tue, 15 Nov 2022 08:12:31 GMT
- Resolution
- One second, which is why it is a weak validator
- Heuristic freshness
- With no Cache-Control, caches may reuse a response for a fraction of its age.
- Ranked below
- ETag. When both are present, the entity tag decides.
In detail
The format is fixed and unforgiving: a three letter day, a two digit date, a three letter month, a four digit year, a 24 hour time, and the literal string GMT. Local time zones, ISO 8601 timestamps and missing leading zeros are all invalid, and a client that cannot parse the value simply treats the header as absent rather than reporting an error.
One second resolution is the reason it is called a weak validator. If a file is modified twice within the same second, the second change is invisible to any client holding the first timestamp, and it will keep being told 304 Not Modified while looking at stale content. That failure is rare on a document site and common on a fast moving API.
The header does more than revalidation. When a response carries no explicit freshness, caches are permitted to invent one, and the usual heuristic is a fraction of the gap between the Date header and Last-Modified. A file untouched for a year can therefore be cached for weeks by a proxy you have never heard of, without any Cache-Control header ever being sent.
Sending it costs almost nothing and it remains the fallback for clients and intermediaries that ignore entity tags altogether. The sensible arrangement is both validators together: an entity tag for accuracy on revalidation, and a Last-Modified date for heuristic freshness and for anything on the path that only knows how to compare timestamps.
Examples
HTTP/1.1 200 OK
Last-Modified: Tue, 15 Nov 2022 08:12:31 GMT
ETag: "9c2f7a1e4b"GET /docs/guide.html HTTP/1.1
If-Modified-Since: Tue, 15 Nov 2022 08:12:31 GMT
HTTP/1.1 304 Not ModifiedThings that catch people out
- A Last-Modified date in the future is treated as invalid by many caches, which then fall back to refetching every time. Clock skew between application servers causes this more often than you would expect.
- Rebuilding a static site rewrites every file timestamp even when the content is identical, so every asset looks modified and every cache refetches.
- The date must be GMT. A server sending a local time zone abbreviation produces a header most clients silently discard.
Try it: HTTP Headers Checker
Check a live URL or paste headers you already have. It grades six security headers from A+ to F and lists every other header the response returned.
HTTP Headers Checker
LOCAL · Browser-OnlyView HTTP response headers for any URL
Enter any website URL to fetch and analyze its HTTP response headers. We'll automatically add https:// if needed.
Why Check Security Headers?
Security headers protect your website against common attacks like XSS, clickjacking, and MIME sniffing. They're set by your web server and cost nothing to implement. Missing security headers are one of the most common findings in security audits.
Related HTTP headers
All http headers →If-Modified-Since: date based conditional requests
How If-Modified-Since produces a 304, why it only applies to GET and HEAD, when servers must ignore it, and why entity tags are preferred where both exist.
ETag: version identifiers for conditional requests
How an ETag is compared, when to use a weak tag, how it drives 304 responses and 412 conflicts, and why compression changes the tag a server sends.
Date: when the response was generated
What the Date header records, the exact GMT format required, how caches use it with Age and max-age, and the one case where a server may omit it.
Cache-Control: directives for browsers and shared caches
Every Cache-Control directive that matters, the difference between no-cache and no-store, and how to cache immutable assets forever without breaking deploys.
Expires: the older way to set a freshness lifetime
What Expires does, why Cache-Control max-age overrides it, what an invalid date such as 0 means to a cache, and whether it is still worth sending.
Age: how long a cached response has been sitting there
What the Age header counts, how it combines with max-age to give remaining freshness, and how to use it to tell a cache hit from a miss at the edge.
Tools for this
- HTTP Headers Checker Free online HTTP headers checker - view HTTP response headers for any URL
- HTTP Request Builder Free online HTTP request builder - build and test HTTP requests with custom headers and body