- Home
- HTTP Headers
- Last-Modified
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.
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 version200 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
| 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/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
Related Headers
- If-Modified-Since - Conditional request header
- If-Unmodified-Since - Conditional request for updates
- ETag - More precise cache validation
- Cache-Control - Caching directives
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.