HTTP

Header

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.

4 min read beginner Try in Playground

What is Last-Modified?

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

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>
```text

**2. Browser caches the response and timestamp.**

**3. Later, browser asks "changed since then?":**

```http
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
```text

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

## Date Format

Last-Modified uses HTTP date format (RFC 7231):

```http
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"
```text

### Database Records

Use record update timestamp:

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

### Dynamic Content

For generated content, use the most recent data timestamp:

```javascript
// 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()
```text

## Conditional Requests

### If-Modified-Since (GET requests)

Check if resource changed since cached version:

```http
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"}
```text

**Server responses:**

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

## Cache Validation Flow

**Initial Request:**

```http
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
```text

Browser reuses cached CSS for another hour.

## Performance Benefits

### Bandwidth Savings

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

### Faster Page Loads

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

## Real-World Examples

### Blog Posts

```http
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"}
```text

### Static Assets

```http
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": "..."}
```text

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

## Best Practices

**1. Always include for cacheable content:**

```http
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()
```javascript

**3. Handle conditional requests:**

```javascript
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.

Keep Learning