How HTTP Works

Response header

< Last-Modified: Tue, 15 Nov 2026 12:45:26 GMT

Last-Modified

Learn how the Last-Modified header indicates when a resource was last changed. Enable efficient cache validation with If-Modified-Since conditional requests.

Reviewed 4 min readbeginner2 sourcesTry itMarkdown
Direction
Response
Category
Caching
Spec
RFC 9110 §8.8.2
On this page

TL;DR: Indicates when a resource was last changed using a timestamp. Enables cache validation with If-Modified-Since for efficient bandwidth usage.

What is Last-Modified?

Last-Modified tells browsers when a resource was last changed. It’s like a timestamp on a file that says “this was updated on January 15th at 3:42 PM.” Browsers use this information to check if their cached version is still current without downloading the entire resource again.

This header enables efficient cache validation and reduces unnecessary data transfers.

How It Works

1. Server sends resource with timestamp:

HTTP/1.1 200 OK
Last-Modified: Wed, 15 Jan 2025 15:42:30 GMT
Content-Type: text/html

<html>...</html>

2. Browser caches the response and timestamp.

3. Later, browser asks “changed since then?”:

GET /page.html HTTP/1.1
If-Modified-Since: Wed, 15 Jan 2025 15:42:30 GMT

4. If unchanged, server responds:

HTTP/1.1 304 Not Modified
Last-Modified: Wed, 15 Jan 2025 15:42:30 GMT

5. Browser reuses cached version - saves bandwidth and time!

Date Format

Last-Modified uses HTTP date format (RFC 9110 section 5.6.7):

Last-Modified: Wed, 15 Jan 2025 15:42:30 GMT

Format: Day, DD Mon YYYY HH:MM:SS GMT

  • Always in GMT/UTC timezone
  • Three-letter day and month names
  • 24-hour time format

Generating Last-Modified

File System

For static files, use file modification time:

const fs = require('fs')
const stats = fs.statSync('index.html')
const lastModified = stats.mtime.toUTCString()
// "Wed, 15 Jan 2025 15:42:30 GMT"

Database Records

Use record update timestamp:

SELECT *, updated_at FROM articles WHERE id = 1;
const lastModified = new Date(article.updated_at).toUTCString()

Dynamic Content

For generated content, use the most recent data timestamp:

// User dashboard - use latest activity
const lastActivity = Math.max(
  user.updated_at,
  user.last_login,
  ...user.notifications.map((n) => n.created_at)
)
const lastModified = new Date(lastActivity).toUTCString()

Application Deployment

For app resources, use build/deploy time:

const buildTime = process.env.BUILD_TIMESTAMP || Date.now()
const lastModified = new Date(buildTime).toUTCString()

Conditional Requests

If-Modified-Since (GET requests)

Check if resource changed since cached version:

GET /api/posts HTTP/1.1
If-Modified-Since: Wed, 15 Jan 2025 15:42:30 GMT

Server responses:

  • 304 Not Modified - No changes, use cached version
  • 200 OK - Resource changed, here’s new content

If-Unmodified-Since (PUT/DELETE requests)

Prevent lost updates by ensuring resource hasn’t changed:

PUT /api/posts/123 HTTP/1.1
If-Unmodified-Since: Wed, 15 Jan 2025 15:42:30 GMT
Content-Type: application/json

{"title": "Updated Post"}

Server responses:

  • 200 OK - Resource unchanged, update successful
  • 412 Precondition Failed - Resource was modified by someone else

Cache Validation Flow

Initial Request:

GET /style.css HTTP/1.1

HTTP/1.1 200 OK
Last-Modified: Wed, 15 Jan 2025 10:30:00 GMT
Cache-Control: max-age=3600
Content-Type: text/css

body { color: blue; }

After Cache Expires:

GET /style.css HTTP/1.1
If-Modified-Since: Wed, 15 Jan 2025 10:30:00 GMT

HTTP/1.1 304 Not Modified
Last-Modified: Wed, 15 Jan 2025 10:30:00 GMT
Cache-Control: max-age=3600

Browser reuses cached CSS for another hour.

Performance Benefits

Bandwidth Savings

File unchanged scenario:
- Without Last-Modified: 50KB transfer
- With Last-Modified: ~200 bytes (304 response)
- Savings: 99.6%

Faster Page Loads

  • No content download for unchanged resources
  • Instant cache revalidation
  • Reduced server load

Real-World Examples

Blog Posts

GET /posts/my-article HTTP/1.1

HTTP/1.1 200 OK
Last-Modified: Mon, 10 Jan 2025 14:20:00 GMT
Content-Type: text/html

<article>...</article>

API Data

GET /api/users/profile HTTP/1.1

HTTP/1.1 200 OK
Last-Modified: Tue, 11 Jan 2025 09:15:30 GMT
Content-Type: application/json

{"name": "John", "email": "john@example.com"}

Static Assets

GET /images/logo.png HTTP/1.1

HTTP/1.1 200 OK
Last-Modified: Fri, 01 Jan 2025 12:00:00 GMT
Content-Type: image/png

[PNG image data]

Last-Modified vs ETag

FeatureLast-ModifiedETag
Precision1-second resolutionExact change detection
GenerationUse timestampHash content or version
ReliabilityCan have clock issuesAlways accurate
PerformanceFaster to generateMore CPU to generate
Use CaseFile-based contentDynamic/API content

Using Both Together

HTTP/1.1 200 OK
Last-Modified: Wed, 15 Jan 2025 15:42:30 GMT
ETag: "abc123"
Content-Type: application/json

{"data": "..."}

Browser will use ETag if available, fall back to Last-Modified.

Getting Last-Modified right

1. Always include for cacheable content:

Last-Modified: Wed, 15 Jan 2025 15:42:30 GMT
Cache-Control: max-age=3600

2. Use precise timestamps:

// ✅ Use actual modification time
const lastModified = file.mtime.toUTCString()

// ❌ Don't use current time
const lastModified = new Date().toUTCString()

3. Handle conditional requests:

app.get('/api/data', (req, res) => {
  const lastModified = data.updated_at.toUTCString()
  const ifModifiedSince = req.headers['if-modified-since']

  if (ifModifiedSince === lastModified) {
    return res.status(304).end()
  }

  res.setHeader('Last-Modified', lastModified)
  res.json(data)
})

4. Use with optimistic locking:

app.put('/api/posts/:id', (req, res) => {
  const post = getPost(req.params.id)
  const lastModified = post.updated_at.toUTCString()
  const ifUnmodifiedSince = req.headers['if-unmodified-since']

  if (ifUnmodifiedSince !== lastModified) {
    return res.status(412).json({ error: 'Post was modified' })
  }

  // Safe to update
  updatePost(req.params.id, req.body)
})

Common Issues

Problem: Clock synchronization across servers Solution: Use database timestamps or centralized time source

Problem: 1-second precision not enough Solution: Use ETag for sub-second changes

Problem: Timezone confusion Solution: Always use GMT/UTC in HTTP headers

Frequently asked questions

What is the Last-Modified header?

Last-Modified indicates when the resource was last changed. Clients use it with If-Modified-Since for cache validation to avoid re-downloading unchanged content.

How does Last-Modified work with caching?

The server sends Last-Modified with the response. On subsequent requests, the client sends If-Modified-Since. If unchanged, the server returns 304 Not Modified.

What is the difference between Last-Modified and ETag?

Last-Modified has second-level precision and uses timestamps. ETag is more precise and works for any content. Use both for maximum compatibility.

What format does Last-Modified use?

Last-Modified uses HTTP-date format in GMT: "Last-Modified: Wed, 21 Oct 2026 07:28:00 GMT". The date should reflect actual content modification time.

Sources

  1. MDN Web Docs: Last-Modifieddeveloper.mozilla.org
  2. RFC 9110: Last-Modifiedrfc-editor.org

Keep going

Browse /search