Request header
> If-Modified-Since: Tue, 15 Nov 2026 12:45:26 GMTIf-Modified-Since Header
Learn how the If-Modified-Since header requests resources only if modified since a specific date. Reduce bandwidth with efficient conditional caching.
- Direction
- Request
- Category
- Conditional requests
- Spec
- RFC 9110 §13.1.3
- JS can set it
- Yes
- CORS-safelisted
- No: may trigger a preflight
On this page
- What is If-Modified-Since?
- How If-Modified-Since Works
- Date Format
- Real-World Examples
- Web Page Caching
- API Data Caching
- Image Caching
- RSS Feed Updates
- Server Response Patterns
- Not Modified (304)
- Modified (200)
- Precondition Failed (412)
- Client Implementation
- JavaScript/Fetch
- Browser Automatic Behavior
- Server Implementation
- Express.js Example
- Static File Serving
- Getting If-Modified-Since right
- Always Set Last-Modified
- Handle Invalid Dates
- Combine with ETag
- Performance Benefits
- Bandwidth Savings
- Reduced Server Load
- Testing
- Using curl
- Browser DevTools
- Timezone Gotchas and Clock Skew
- Related Headers
TL;DR: Requests resources only if modified after a specific date, enabling efficient caching. Server returns 304 Not Modified for unchanged content, saving bandwidth.
What is If-Modified-Since?
The If-Modified-Since header makes conditional requests based on modification time. It’s like asking “only send me this file if it’s newer than what I already have.” This saves bandwidth and improves performance by avoiding unnecessary downloads.
This header is essential for efficient caching and reducing server load.
How If-Modified-Since Works
1. Client requests with condition:
GET /api/posts HTTP/1.1
Host: example.com
If-Modified-Since: Wed, 17 Jan 2026 10:30:00 GMT
2a. Resource unchanged (304 response):
HTTP/1.1 304 Not Modified
Last-Modified: Wed, 17 Jan 2026 10:30:00 GMT
Cache-Control: max-age=3600
2b. Resource modified (200 response):
HTTP/1.1 200 OK
Last-Modified: Thu, 18 Jan 2026 14:20:00 GMT
Content-Type: application/json
{"posts": [...]}
Date Format
Uses HTTP-date format (RFC 9110 section 5.6.7):
If-Modified-Since: Wed, 18 Jan 2026 10:30:00 GMT
If-Modified-Since: Thu, 19 Jan 2026 15:45:30 GMT
Real-World Examples
Web Page Caching
GET /index.html HTTP/1.1
Host: example.com
If-Modified-Since: Tue, 16 Jan 2026 08:00:00 GMT
API Data Caching
GET /api/products HTTP/1.1
Host: shop.example.com
If-Modified-Since: Wed, 17 Jan 2026 12:00:00 GMT
Accept: application/json
Image Caching
GET /images/logo.png HTTP/1.1
Host: cdn.example.com
If-Modified-Since: Mon, 15 Jan 2026 09:30:00 GMT
RSS Feed Updates
GET /feed.xml HTTP/1.1
Host: blog.example.com
If-Modified-Since: Thu, 18 Jan 2026 06:00:00 GMT
Server Response Patterns
Not Modified (304)
HTTP/1.1 304 Not Modified
Last-Modified: Wed, 17 Jan 2026 10:30:00 GMT
ETag: "abc123"
Cache-Control: max-age=3600
Client should use cached version.
Modified (200)
HTTP/1.1 200 OK
Last-Modified: Thu, 18 Jan 2026 14:20:00 GMT
Content-Type: text/html
Content-Length: 1247
<html>...</html>
Resource has changed, here’s the new content.
Precondition Failed (412)
HTTP/1.1 412 Precondition Failed
Content-Type: application/json
{"error": "Invalid If-Modified-Since date"}
Client Implementation
JavaScript/Fetch
// Store last modified date
let lastModified = localStorage.getItem('posts-last-modified')
const headers = {}
if (lastModified) {
headers['If-Modified-Since'] = lastModified
}
const response = await fetch('/api/posts', { headers })
if (response.status === 304) {
// Use cached data
const cachedData = JSON.parse(localStorage.getItem('posts-data'))
return cachedData
} else if (response.ok) {
// Update cache with new data
const newData = await response.json()
const newLastModified = response.headers.get('Last-Modified')
localStorage.setItem('posts-data', JSON.stringify(newData))
localStorage.setItem('posts-last-modified', newLastModified)
return newData
}
Browser Automatic Behavior
// Browsers automatically add If-Modified-Since for cached resources
// when you refresh a page or revisit it
Server Implementation
Express.js Example
app.get('/api/posts', (req, res) => {
const posts = getPosts()
const lastModified = getPostsLastModified()
// Check If-Modified-Since header
const ifModifiedSince = req.headers['if-modified-since']
if (ifModifiedSince) {
const clientDate = new Date(ifModifiedSince)
if (lastModified <= clientDate) {
// Not modified
return res.status(304).set('Last-Modified', lastModified.toUTCString()).end()
}
}
// Modified or first request
res.set('Last-Modified', lastModified.toUTCString())
res.json(posts)
})
Static File Serving
// Most web servers handle this automatically
app.use(
express.static('public', {
lastModified: true, // Enable Last-Modified headers
etag: true // Also enable ETag for better caching
})
)
Getting If-Modified-Since right
Always Set Last-Modified
// ✅ Always include Last-Modified in responses
res.set('Last-Modified', resource.updatedAt.toUTCString())
res.json(resource)
Handle Invalid Dates
app.get('/resource', (req, res) => {
const ifModifiedSince = req.headers['if-modified-since']
if (ifModifiedSince) {
const clientDate = new Date(ifModifiedSince)
// Check for invalid date
if (isNaN(clientDate.getTime())) {
return res.status(400).json({ error: 'Invalid If-Modified-Since date' })
}
// Continue with comparison...
}
})
Combine with ETag
app.get('/data', (req, res) => {
const data = getData()
const lastModified = data.updatedAt
const etag = generateETag(data)
// Check both conditions
const ifModifiedSince = req.headers['if-modified-since']
const ifNoneMatch = req.headers['if-none-match']
let notModified = false
if (ifModifiedSince) {
notModified = lastModified <= new Date(ifModifiedSince)
}
if (ifNoneMatch) {
notModified = notModified && ifNoneMatch === etag
}
if (notModified) {
return res.status(304).set('Last-Modified', lastModified.toUTCString()).set('ETag', etag).end()
}
res.set('Last-Modified', lastModified.toUTCString())
res.set('ETag', etag)
res.json(data)
})
Performance Benefits
Bandwidth Savings
Without If-Modified-Since:
- Request: 500 bytes
- Response: 50KB (full content)
- Total: ~50KB
With If-Modified-Since (304):
- Request: 600 bytes (with header)
- Response: 200 bytes (304 status)
- Total: ~800 bytes (99% savings!)
Reduced Server Load
- Skip database queries for unchanged data
- Avoid JSON serialization
- Reduce CPU and memory usage
Testing
Using curl
# First request (gets Last-Modified)
curl -v https://example.com/api/posts
# Conditional request
curl -H "If-Modified-Since: Wed, 18 Jan 2026 10:30:00 GMT" \
https://example.com/api/posts
# Should return 304 if not modified
Browser DevTools
- Open Network tab
- Load page normally
- Refresh page (Ctrl+R)
- Look for 304 responses with If-Modified-Since headers
Timezone Gotchas and Clock Skew
The If-Modified-Since header uses HTTP-date format, which is always in GMT. A common mistake is generating the date in local time or using a format that includes a timezone offset. Servers must parse the date correctly and compare it against the resource’s last-modified time in UTC.
Clock skew between client and server can cause unexpected behavior. If the client’s clock is ahead of the server’s clock, the client might send an If-Modified-Since date that is in the server’s future, causing the server to always return 304 even for recently modified resources. This is one reason why If-None-Match with ETags is generally more reliable than If-Modified-Since for cache validation — ETags are based on content identity rather than time.
The If-Modified-Since header is also less precise than ETags for resources that change multiple times within a single second. If a resource is modified twice in the same second, the second modification has the same Last-Modified timestamp as the first, so a client that cached the first version would incorrectly receive a 304 for the second version. ETags avoid this problem because they are based on content hash rather than modification time.
Related Headers
- Last-Modified - Server’s modification timestamp
- If-None-Match - ETag-based conditional requests
- Cache-Control - Caching directives
- ETag - Resource version identifier
Frequently asked questions
What is If-Modified-Since?
If-Modified-Since is a conditional request header. The client sends the Last-Modified date from a cached response, and the server returns 304 if unchanged or 200 with new content.
How does If-Modified-Since improve performance?
It avoids re-downloading unchanged resources. The server only sends the full response if the resource changed after the specified date, saving bandwidth.
What is the difference between If-Modified-Since and If-None-Match?
If-Modified-Since uses timestamps with Last-Modified. If-None-Match uses ETags for more precise validation. If-None-Match takes precedence when both are present.
What format does If-Modified-Since use?
It uses HTTP-date format in GMT: "If-Modified-Since: Wed, 21 Oct 2026 07:28:00 GMT". The date comes from the Last-Modified header of the cached response.
Sources
Related
If-None-Match Header
Learn how the If-None-Match header makes conditional requests using ETags. Avoid downloading unchanged resources and reduce bandwidth with cache validation.
If-Unmodified-Since Header
Learn how the If-Unmodified-Since header makes requests conditional on resources not being modified. Prevent conflicts in concurrent update scenarios.
If-Match Header
Learn how the If-Match header makes requests conditional based on ETag matching. Prevent conflicts and lost updates in concurrent editing scenarios.
If-Range Header
Learn how the If-Range header requests partial content only if the resource is unchanged. Efficiently resume downloads without re-fetching entire files.