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

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

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

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

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

```http
GET /index.html HTTP/1.1
Host: example.com
If-Modified-Since: Tue, 16 Jan 2026 08:00:00 GMT
```

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

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

```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
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
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
HTTP/1.1 412 Precondition Failed
Content-Type: application/json

{"error": "Invalid If-Modified-Since date"}
```

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

```javascript
// Browsers automatically add If-Modified-Since for cached resources
// when you refresh a page or revisit it
```

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

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

```javascript
// ✅ Always include Last-Modified in responses
res.set('Last-Modified', resource.updatedAt.toUTCString())
res.json(resource)
```

### Handle Invalid Dates

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

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

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

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

## Related Headers

- [Last-Modified](https://howhttpworks.com/headers/last-modified) - Server's modification timestamp
- [If-None-Match](https://howhttpworks.com/headers/if-none-match) - ETag-based conditional requests
- [Cache-Control](https://howhttpworks.com/headers/cache-control) - Caching directives
- [ETag](https://howhttpworks.com/headers/etag) - Resource version identifier
