# 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.

Source: https://howhttpworks.com/headers/last-modified
Last reviewed: 2026-10-04

> **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
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?":**

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

**4. If unchanged, server responds:**

```http
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):

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

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

```sql
SELECT *, updated_at FROM articles WHERE id = 1;
```

```javascript
const lastModified = new Date(article.updated_at).toUTCString()
```

### 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:

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

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

```http
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:**

```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:**

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

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

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

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

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

| Feature         | Last-Modified         | ETag                    |
| --------------- | --------------------- | ----------------------- |
| **Precision**   | 1-second resolution   | Exact change detection  |
| **Generation**  | Use timestamp         | Hash content or version |
| **Reliability** | Can have clock issues | Always accurate         |
| **Performance** | Faster to generate    | More CPU to generate    |
| **Use Case**    | File-based content    | Dynamic/API content     |

### Using Both Together

```http
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:**

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

**2. Use precise timestamps:**

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

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

**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:**

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

## Related Headers

- [If-Modified-Since](https://howhttpworks.com/headers/if-modified-since) - Conditional request header
- [If-Unmodified-Since](https://howhttpworks.com/headers/if-unmodified-since) - Conditional request for updates
- [ETag](https://howhttpworks.com/headers/etag) - More precise cache validation
- [Cache-Control](https://howhttpworks.com/headers/cache-control) - Caching directives
