HTTP

Header

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

4 min read intermediate Try in Playground

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

**2a. Resource unchanged (304 response):**

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

## Date Format

Uses HTTP-date format (RFC 7231):

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

### API Data Caching

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

### RSS Feed Updates

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

Client should use cached version.

### Modified (200)

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

## Client Implementation

### JavaScript/Fetch

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

## Server Implementation

### Express.js Example

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

## Best Practices

### Always Set Last-Modified

```javascript
// ✅ 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...
  }
})
```javascript

### Combine with ETag

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

  1. Open Network tab
  2. Load page normally
  3. Refresh page (Ctrl+R)
  4. 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.

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.

Keep Learning