- Home
- HTTP Status Codes
- 304 Not Modified
Status Code
304 Not Modified
Cached response is still valid. Learn how 304 Not Modified improves performance through conditional requests and caching.
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 Header | Server Comparison | Purpose |
|---|---|---|
| If-None-Match | Compares with ETag | Entity tag validation |
| If-Modified-Since | Compares with Last-Modified | Timestamp validation |
| If-Unmodified-Since | Compares with Last-Modified | Prevent lost updates |
| If-Match | Compares with ETag | Ensure specific version |
304 vs Other Caching Responses
| Code | Meaning | Response Body | When to Use |
|---|---|---|---|
| 304 | Not modified | None (use cache) | Resource unchanged since last request |
| 200 | OK | Full content | Resource changed or no cache validation |
| 412 | Precondition failed | Error message | Conditional 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:
- Set method to GET
- Set path to /cached-demo
- Add header:
If-None-Match: "demo-v1" - Click Send request
- Watch the 304 response (no body!)
Related Status Codes
- 200 OK - Resource has changed, sending full content
- 412 Precondition Failed - Conditional request validation failed
- Last-Modified Header - Timestamp-based validation
- ETag Header - Content-based validation
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.