HTTP

Header

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.

8 min read advanced Try in Playground

What is If-Unmodified-Since?

TL;DR: Makes requests conditional on resource not being modified since a specific date. Used to prevent lost updates by ensuring no one else changed the resource.

The If-Unmodified-Since header makes a request conditional by specifying that the server should only process the request if the resource hasn’t been modified since the given date. It’s like saying “only do this if nothing has changed since I last checked.”

This is the time-based equivalent of If-Match and is useful for preventing concurrent modifications and ensuring data consistency.

How If-Unmodified-Since Works

Client fetches resource:

GET /api/posts/123 HTTP/1.1
Host: api.example.com

HTTP/1.1 200 OK
Last-Modified: Sat, 18 Jan 2026 10:00:00 GMT
Content-Type: application/json

{"id": 123, "title": "My Post"}
```text

**Client updates with condition:**

```http
PUT /api/posts/123 HTTP/1.1
Host: api.example.com
If-Unmodified-Since: Sat, 18 Jan 2026 10:00:00 GMT
Content-Type: application/json

{"id": 123, "title": "Updated Post"}

If not modified, update succeeds:

HTTP/1.1 200 OK
Last-Modified: Sat, 18 Jan 2026 11:00:00 GMT

{"id": 123, "title": "Updated Post"}
```text

**If modified, update fails:**

```http
HTTP/1.1 412 Precondition Failed

{"error": "Resource has been modified"}

Syntax

If-Unmodified-Since: <http-date>
```text

### Format

Uses HTTP-date format:

```http
If-Unmodified-Since: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT

Common Examples

Conditional Update

If-Unmodified-Since: Sat, 18 Jan 2026 10:00:00 GMT
```text

Only update if resource hasn't changed since this time.

### Conditional Delete

```http
If-Unmodified-Since: Fri, 17 Jan 2026 15:30:00 GMT

Only delete if resource is still from this version.

Partial Update

If-Unmodified-Since: Sat, 18 Jan 2026 09:00:00 GMT
```http

PATCH only if nothing changed since 9 AM.

## Real-World Scenarios

### Preventing Lost Updates

```http
# User A fetches the document
GET /api/documents/456 HTTP/1.1

HTTP/1.1 200 OK
Last-Modified: Sat, 18 Jan 2026 10:00:00 GMT

{"id": 456, "content": "Original content"}

# User A updates with condition
PUT /api/documents/456 HTTP/1.1
If-Unmodified-Since: Sat, 18 Jan 2026 10:00:00 GMT

{"content": "User A's changes"}

# User B already modified it
HTTP/1.1 412 Precondition Failed
Last-Modified: Sat, 18 Jan 2026 10:30:00 GMT

{"error": "Document was modified at 10:30"}

Safe File Upload

# Check current file timestamp
HEAD /files/report.pdf HTTP/1.1

HTTP/1.1 200 OK
Last-Modified: Sat, 18 Jan 2026 08:00:00 GMT

# Upload only if not modified
PUT /files/report.pdf HTTP/1.1
If-Unmodified-Since: Sat, 18 Jan 2026 08:00:00 GMT
Content-Type: application/pdf

[PDF data]

HTTP/1.1 200 OK
Last-Modified: Sat, 18 Jan 2026 12:00:00 GMT
```text

### Conditional Range Request

```http
# Download part of a file, but only if it hasn't changed
GET /large-file.zip HTTP/1.1
Range: bytes=1000000-2000000
If-Unmodified-Since: Fri, 17 Jan 2026 14:00:00 GMT

# File hasn't changed - send range
HTTP/1.1 206 Partial Content
Content-Range: bytes 1000000-2000000/10000000
Last-Modified: Fri, 17 Jan 2026 14:00:00 GMT

[Requested bytes]

Collaborative Editing

# Editor checks last modification time
GET /api/articles/789 HTTP/1.1

HTTP/1.1 200 OK
Last-Modified: Sat, 18 Jan 2026 09:00:00 GMT

{"id": 789, "text": "Article content"}

# Submit changes only if no one else edited
PUT /api/articles/789 HTTP/1.1
If-Unmodified-Since: Sat, 18 Jan 2026 09:00:00 GMT

{"text": "Modified article content"}

HTTP/1.1 200 OK
Last-Modified: Sat, 18 Jan 2026 12:30:00 GMT
```javascript

## Server Implementation

### Express.js (Node.js)

```javascript
const express = require('express')
const app = express()

// Mock database with timestamps
const resources = {
  123: {
    id: 123,
    title: 'My Post',
    lastModified: new Date('2026-01-18T10:00:00Z')
  }
}

// GET endpoint
app.get('/api/posts/:id', (req, res) => {
  const post = resources[req.params.id]
  if (!post) {
    return res.status(404).json({ error: 'Not found' })
  }

  res.setHeader('Last-Modified', post.lastModified.toUTCString())
  res.json(post)
})

// PUT with If-Unmodified-Since check
app.put('/api/posts/:id', express.json(), (req, res) => {
  const post = resources[req.params.id]
  if (!post) {
    return res.status(404).json({ error: 'Not found' })
  }

  const ifUnmodifiedSince = req.headers['if-unmodified-since']

  if (ifUnmodifiedSince) {
    const conditionDate = new Date(ifUnmodifiedSince)
    const lastModified = new Date(post.lastModified)

    // If resource was modified after condition date, fail
    if (lastModified > conditionDate) {
      res.setHeader('Last-Modified', lastModified.toUTCString())
      return res.status(412).json({
        error: 'Precondition Failed',
        message: 'Resource has been modified',
        lastModified: lastModified.toISOString()
      })
    }
  }

  // Update the post
  Object.assign(post, req.body)
  post.lastModified = new Date()

  res.setHeader('Last-Modified', post.lastModified.toUTCString())
  res.json(post)
})

// DELETE with If-Unmodified-Since
app.delete('/api/posts/:id', (req, res) => {
  const post = resources[req.params.id]
  if (!post) {
    return res.status(404).json({ error: 'Not found' })
  }

  const ifUnmodifiedSince = req.headers['if-unmodified-since']

  if (ifUnmodifiedSince) {
    const conditionDate = new Date(ifUnmodifiedSince)
    const lastModified = new Date(post.lastModified)

    if (lastModified > conditionDate) {
      return res.status(412).json({
        error: 'Resource has been modified since ' + ifUnmodifiedSince
      })
    }
  }

  delete resources[req.params.id]
  res.status(204).end()
})

Middleware for Conditional Requests

function checkIfUnmodifiedSince(req, res, next) {
  const ifUnmodifiedSince = req.headers['if-unmodified-since']

  if (!ifUnmodifiedSince) {
    return next()
  }

  const resource = getResource(req.params.id)
  if (!resource) {
    return res.status(404).json({ error: 'Not found' })
  }

  const conditionDate = new Date(ifUnmodifiedSince)
  const lastModified = new Date(resource.lastModified)

  // Check if modified after condition
  if (lastModified > conditionDate) {
    res.setHeader('Last-Modified', lastModified.toUTCString())
    return res.status(412).json({
      error: 'Precondition Failed',
      lastModified: lastModified.toISOString()
    })
  }

  req.resource = resource
  next()
}

app.put('/api/posts/:id', checkIfUnmodifiedSince, (req, res) => {
  // Resource validated, proceed with update
  updateResource(req.resource, req.body)
  res.json(req.resource)
})
```javascript

### FastAPI (Python)

```python
from fastapi import FastAPI, Request, HTTPException, Response
from datetime import datetime
from email.utils import formatdate, parsedate_to_datetime

app = FastAPI()

# Mock database
resources = {
    123: {
        "id": 123,
        "title": "My Post",
        "lastModified": datetime(2026, 1, 18, 10, 0, 0)
    }
}

@app.get("/api/posts/{post_id}")
async def get_post(post_id: int, response: Response):
    post = resources.get(post_id)
    if not post:
        raise HTTPException(status_code=404, detail="Not found")

    last_modified = formatdate(
        timeval=post["lastModified"].timestamp(),
        localtime=False,
        usegmt=True
    )
    response.headers["Last-Modified"] = last_modified

    return post

@app.put("/api/posts/{post_id}")
async def update_post(
    post_id: int,
    data: dict,
    request: Request,
    response: Response
):
    post = resources.get(post_id)
    if not post:
        raise HTTPException(status_code=404, detail="Not found")

    if_unmodified_since = request.headers.get("if-unmodified-since")

    if if_unmodified_since:
        try:
            condition_date = parsedate_to_datetime(if_unmodified_since)
            last_modified = post["lastModified"]

            # Remove timezone info for comparison
            condition_date = condition_date.replace(tzinfo=None)

            # If modified after condition date, fail
            if last_modified > condition_date:
                last_modified_str = formatdate(
                    timeval=last_modified.timestamp(),
                    localtime=False,
                    usegmt=True
                )
                raise HTTPException(
                    status_code=412,
                    detail={
                        "error": "Precondition Failed",
                        "lastModified": last_modified.isoformat()
                    },
                    headers={"Last-Modified": last_modified_str}
                )
        except Exception as e:
            # Invalid date format
            pass

    # Update post
    post.update(data)
    post["lastModified"] = datetime.utcnow()

    last_modified = formatdate(
        timeval=post["lastModified"].timestamp(),
        localtime=False,
        usegmt=True
    )
    response.headers["Last-Modified"] = last_modified

    return post

Django

from django.http import JsonResponse, HttpResponse
from django.views.decorators.http import require_http_methods
from django.utils.http import parse_http_date, http_date
from datetime import datetime
import json

@require_http_methods(["PUT"])
def update_post(request, post_id):
    post = get_post_from_db(post_id)
    if not post:
        return JsonResponse({'error': 'Not found'}, status=404)

    if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE')

    if if_unmodified_since:
        try:
            condition_timestamp = parse_http_date(if_unmodified_since)
            last_modified_timestamp = post.last_modified.timestamp()

            # If modified after condition, fail
            if last_modified_timestamp > condition_timestamp:
                response = JsonResponse({
                    'error': 'Precondition Failed',
                    'lastModified': post.last_modified.isoformat()
                }, status=412)
                response['Last-Modified'] = http_date(last_modified_timestamp)
                return response
        except:
            pass  # Invalid date format

    # Update post
    data = json.loads(request.body)
    post.update(data)
    post.last_modified = datetime.utcnow()
    post.save()

    response = JsonResponse(post.to_dict())
    response['Last-Modified'] = http_date(post.last_modified.timestamp())

    return response
```javascript

## Best Practices

### For Servers

**1. Compare dates correctly**

```javascript
// ✅ Proper date comparison
const conditionDate = new Date(ifUnmodifiedSince)
const lastModified = new Date(resource.lastModified)

if (lastModified > conditionDate) {
  return res.status(412).send()
}

// Note: Use > not >= (equal means unmodified)

2. Return current Last-Modified on 412

# ✅ Help client understand what happened
HTTP/1.1 412 Precondition Failed
Last-Modified: Sat, 18 Jan 2026 11:00:00 GMT

{"error": "Modified at 11:00"}
```javascript

**3. Handle invalid dates gracefully**

```javascript
try {
  const conditionDate = new Date(ifUnmodifiedSince)
  if (isNaN(conditionDate.getTime())) {
    // Invalid date - ignore the header
    return next()
  }
} catch (err) {
  // Ignore invalid header
  return next()
}

4. Use with If-Match for stronger validation

// Check both conditions if present
if (ifMatch && currentETag !== ifMatch) {
  return res.status(412).send()
}

if (ifUnmodifiedSince && lastModified > new Date(ifUnmodifiedSince)) {
  return res.status(412).send()
}
```text

**5. Set Last-Modified on successful updates**

```http
HTTP/1.1 200 OK
Last-Modified: Sat, 18 Jan 2026 12:00:00 GMT

For Clients

1. Store Last-Modified for updates

// ✅ Fetch and store timestamp
const response = await fetch('/api/post/123')
const post = await response.json()
const lastModified = response.headers.get('Last-Modified')

// Use for conditional update
await fetch('/api/post/123', {
  method: 'PUT',
  headers: {
    'If-Unmodified-Since': lastModified,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(updates)
})
```javascript

**2. Handle 412 responses**

```javascript
const response = await fetch('/api/post/123', {
  method: 'PUT',
  headers: {
    'If-Unmodified-Since': storedLastModified
  },
  body: JSON.stringify(updates)
})

if (response.status === 412) {
  // Resource was modified - refetch and show conflict
  const latest = await fetch('/api/post/123')
  const latestData = await latest.json()

  showConflictResolution(updates, latestData)
}

3. Prefer If-Match over If-Unmodified-Since

// ✅ ETag is more precise
const etag = response.headers.get('ETag')
if (etag) {
  headers['If-Match'] = etag
} else {
  // Fall back to If-Unmodified-Since
  headers['If-Unmodified-Since'] = response.headers.get('Last-Modified')
}
```text

## If-Unmodified-Since vs If-Match

### If-Unmodified-Since (Time-Based)

```http
# "Only update if nothing changed since this time"
PUT /resource
If-Unmodified-Since: Sat, 18 Jan 2026 10:00:00 GMT

# Granularity: 1 second
# Can have false matches (same timestamp, different content)

If-Match (Version-Based)

# "Only update if it's still exactly this version"
PUT /resource
If-Match: "abc123"

# More precise - different content = different ETag
# Recommended when available
```text

## Common Use Cases

### Optimistic Locking

```http
If-Unmodified-Since: Sat, 18 Jan 2026 10:00:00 GMT

Prevent concurrent modification conflicts.

Safe Updates

If-Unmodified-Since: Fri, 17 Jan 2026 15:00:00 GMT
```text

Only update if resource is still the expected version.

### Conditional Deletes

```http
DELETE /resource
If-Unmodified-Since: Sat, 18 Jan 2026 09:00:00 GMT

Only delete if resource hasn’t been updated.

Range Requests

Range: bytes=1000-2000
If-Unmodified-Since: Sat, 18 Jan 2026 08:00:00 GMT
```text

Only send range if file hasn't changed.

## Testing If-Unmodified-Since

### Using curl

```bash
# Fetch Last-Modified
LAST_MODIFIED=$(curl -sI https://api.example.com/posts/123 | \
  grep -i "Last-Modified:" | cut -d' ' -f2-)

# Update with condition
curl -X PUT https://api.example.com/posts/123 \
  -H "If-Unmodified-Since: $LAST_MODIFIED" \
  -H "Content-Type: application/json" \
  -d '{"title": "New Title"}'

# Test with old date (should fail)
curl -X PUT https://api.example.com/posts/123 \
  -H "If-Unmodified-Since: Mon, 01 Jan 2020 00:00:00 GMT" \
  -d '{"title": "New Title"}'

Using JavaScript

// Complete update with optimistic locking
async function updateWithTimestamp(id, updates) {
  // Fetch current version
  const getResponse = await fetch(`/api/posts/${id}`)
  const currentData = await getResponse.json()
  const lastModified = getResponse.headers.get('Last-Modified')

  // Update with condition
  const updateResponse = await fetch(`/api/posts/${id}`, {
    method: 'PUT',
    headers: {
      'If-Unmodified-Since': lastModified,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(updates)
  })

  if (updateResponse.status === 412) {
    throw new Error('Resource was modified by another user')
  }

  return updateResponse.json()
}

Frequently Asked Questions

What is If-Unmodified-Since?

If-Unmodified-Since is a precondition header. The server only processes the request if the resource has not been modified since the specified date.

When is If-Unmodified-Since used?

Use it with PUT/PATCH/DELETE to prevent overwriting changes made by others. If the resource changed after your specified date, the server returns 412.

What happens if the condition fails?

The server returns 412 Precondition Failed. You should fetch the latest version, resolve conflicts, and retry with the new Last-Modified date.

Should I use If-Unmodified-Since or If-Match?

If-Match with ETags is more precise. If-Unmodified-Since has second-level granularity. Use If-Match when available; fall back to If-Unmodified-Since otherwise.

Keep Learning