HTTP

Status Code

304 Not Modified

Cached response is still valid. Learn how 304 Not Modified improves performance through conditional requests and caching.

4 min read intermediate Try in Playground

TL;DR: 304 Not Modified means use your cached copy. The resource hasn’t changed since you last downloaded it.

A 304 Not Modified status code means the resource hasn’t changed since the client last downloaded it, so they can use their cached version. Think of it like checking if your favorite book has a new edition—if the librarian says “nope, same edition as last time,” you don’t need to check it out again because you already have it at home.

This status code is a performance optimization that saves bandwidth and speeds up web browsing.

When Does This Happen?

You’ll see a 304 Not Modified response in these common situations:

1. Browser Cache Validation

Browser has cached image from yesterday
Checks if it's still current with If-Modified-Since header
Server responds 304 if unchanged

2. API Response Caching

Mobile app cached user profile data
Sends If-None-Match with ETag value
Server responds 304 if profile unchanged

3. Static Asset Optimization

CSS/JS files cached by CDN
CDN checks origin server for updates
Origin responds 304 if files unchanged

4. Feed/Content Updates

RSS reader checks for new blog posts
Sends last-modified timestamp
Server responds 304 if no new posts

5. Database Query Optimization

Client cached expensive report data
Includes ETag in subsequent requests
Server responds 304 if data unchanged

Example Responses

Image Cache Validation:

HTTP/1.1 304 Not Modified
Date: Sat, 18 Jan 2026 12:00:00 GMT
ETag: "abc123"
Cache-Control: max-age=3600
Last-Modified: Fri, 17 Jan 2026 10:00:00 GMT

(no response body - client uses cached version)
```text

**API Data Validation:**

```http
HTTP/1.1 304 Not Modified
ETag: "user-profile-v5"
Cache-Control: private, max-age=300
Vary: Authorization

(no response body)

Static Asset Check:

HTTP/1.1 304 Not Modified
ETag: "styles-v2.1.0"
Cache-Control: public, max-age=31536000
Last-Modified: Mon, 15 Jan 2026 08:30:00 GMT

(no response body)
```text

## Real-World Example

Imagine a news website where a user's browser checks if an article has been updated:

**Initial Request (first visit):**

```http
GET /articles/breaking-news HTTP/1.1
Host: news.com

Response:
HTTP/1.1 200 OK
ETag: "article-v1"
Last-Modified: Sat, 18 Jan 2026 09:00:00 GMT
Cache-Control: max-age=300

<html>...article content...</html>

Subsequent Request (5 minutes later):

GET /articles/breaking-news HTTP/1.1
Host: news.com
If-None-Match: "article-v1"
If-Modified-Since: Sat, 18 Jan 2026 09:00:00 GMT
```text

**304 Response (article unchanged):**

```http
HTTP/1.1 304 Not Modified
ETag: "article-v1"
Cache-Control: max-age=300
Date: Sat, 18 Jan 2026 09:05:00 GMT

(no body - browser uses cached article)

Conditional Request Headers

Client HeaderServer ComparisonPurpose
If-None-MatchCompares with ETagEntity tag validation
If-Modified-SinceCompares with Last-ModifiedTimestamp validation
If-Unmodified-SinceCompares with Last-ModifiedPrevent lost updates
If-MatchCompares with ETagEnsure specific version

304 vs Other Caching Responses

CodeMeaningResponse BodyWhen to Use
304Not modifiedNone (use cache)Resource unchanged since last request
200OKFull contentResource changed or no cache validation
412Precondition failedError messageConditional request failed

Performance Benefits

Bandwidth Savings:

Without 304: 50KB image downloaded every time
With 304: Only headers (~200 bytes) when unchanged
Savings: 99.6% bandwidth reduction

Speed Improvements:

Full download: 500ms over slow connection
304 response: 50ms (headers only)
Speed improvement: 10x faster

Common Mistakes

❌ Including response body with 304

HTTP/1.1 304 Not Modified
Content-Type: application/json

{"data": "..."}  ← Wrong! 304 must have no body
```text

**❌ Wrong cache headers**

```http
HTTP/1.1 304 Not Modified
Cache-Control: no-cache  ← Contradicts the caching purpose

❌ Missing validation headers

HTTP/1.1 304 Not Modified
← Missing ETag or Last-Modified for future validation
```text

**✅ Correct usage**

```http
HTTP/1.1 304 Not Modified
ETag: "resource-v2"
Cache-Control: max-age=3600

(no response body)

Implementation Examples

Express.js with ETags:

app.get('/api/data', (req, res) => {
  const data = getData()
  const etag = generateETag(data)

  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end()
  }

  res.set('ETag', etag).json(data)
})
```text

**PHP with Last-Modified:**

```php
$lastModified = filemtime('data.json');
$ifModifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE'] ?? '';

if (strtotime($ifModifiedSince) >= $lastModified) {
    header('HTTP/1.1 304 Not Modified');
    header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
    exit;
}

// Send full response
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
echo file_get_contents('data.json');

Best Practices

Always Include Validation Headers:

HTTP/1.1 304 Not Modified
ETag: "resource-version-123"
Last-Modified: Sat, 18 Jan 2026 10:00:00 GMT
```text

**Set Appropriate Cache-Control:**

```http
HTTP/1.1 304 Not Modified
Cache-Control: private, max-age=300  ← For user-specific data
Cache-Control: public, max-age=3600  ← For public resources

Handle Multiple Conditions:

// Check both ETag and Last-Modified
if (clientETag === serverETag || clientModifiedSince >= serverModified) {
  return 304
}

Browser Behavior

Automatic Validation:

  • Browser automatically sends If-None-Match/If-Modified-Since
  • Happens when cache expires or user refreshes
  • No JavaScript code needed

Cache Usage:

  • 304 response triggers use of cached content
  • Browser renders page from local cache
  • Saves download time and bandwidth

Try It Yourself

Visit our request builder and test cache validation:

  1. Set method to GET
  2. Set path to /cached-demo
  3. Add header: If-None-Match: "demo-v1"
  4. Click Send request
  5. Watch the 304 response (no body!)

Frequently Asked Questions

What does 304 Not Modified mean?

A 304 response means the cached version of the resource is still valid. The server tells the client to use its cached copy instead of downloading the resource again.

How does 304 improve performance?

It saves bandwidth by not re-downloading unchanged resources. The client sends If-None-Match or If-Modified-Since headers, and if the resource has not changed, the server returns 304 with no body.

What headers trigger a 304 response?

If-None-Match (with ETag) and If-Modified-Since (with Last-Modified date) are conditional headers that can trigger 304. The server compares these with current resource state.

Does 304 have a response body?

No, 304 responses must not include a body. The client already has the content cached. The response only includes headers to update cache metadata if needed.

Keep Learning