304
Not Modified
3xx Redirection
What Does HTTP 304 Not Modified Mean?
HTTP 304 Not Modified is a response to a conditional request. It tells the client that the resource has not been modified since the last time it was fetched, so the cached version can be reused. The server sends only headers — no body — saving bandwidth and improving load times.
This status code is a cornerstone of HTTP caching. It works through two mechanisms:
- ETag + If-None-Match: The server assigns a unique fingerprint (ETag) to each version of a resource. On subsequent requests, the browser sends
If-None-Match: "etag-value". If the ETag still matches, the server returns 304. - Last-Modified + If-Modified-Since: The server reports when the resource was last changed. The browser sends
If-Modified-Since: Thu, 15 Jan 2025 10:30:00 GMT. If the resource has not changed since that date, the server returns 304.
Common Causes
- Browser revalidation: When a cached resource's
max-ageexpires, the browser sends a conditional request to check if the cache is still valid rather than downloading the full resource again. - CDN cache validation: CDN edge nodes send conditional requests to origin servers to revalidate their cached copies, minimizing origin load and bandwidth.
- API polling: Applications that poll an API endpoint for changes can use conditional requests to avoid processing unchanged data. This is common with feeds, notifications, and sync endpoints.
- Static asset serving: CSS, JavaScript, and image files commonly trigger 304 responses since they change infrequently. The server checks the ETag or file modification time before responding.
How Conditional Requests Work
ETag-based flow
# First request - server provides ETag
$ curl -I https://example.com/style.css
HTTP/1.1 200 OK
ETag: "abc123"
Content-Type: text/css
Content-Length: 45230
# Second request - browser sends If-None-Match
$ curl -I -H 'If-None-Match: "abc123"' https://example.com/style.css
HTTP/1.1 304 Not Modified
ETag: "abc123"
# No body sent - browser uses cached version
Last-Modified flow
# First request
$ curl -I https://example.com/data.json
HTTP/1.1 200 OK
Last-Modified: Wed, 15 Jan 2025 10:00:00 GMT
Content-Type: application/json
# Second request - browser sends If-Modified-Since
$ curl -I -H 'If-Modified-Since: Wed, 15 Jan 2025 10:00:00 GMT' \
https://example.com/data.json
HTTP/1.1 304 Not Modified
# Cached version is still current
Nginx ETag Configuration
# ETags are enabled by default in Nginx for static files
# To explicitly control it:
location /static/ {
etag on; # Enable ETags (default)
if_modified_since exact; # Exact timestamp matching
expires 1h; # Cache for 1 hour, then revalidate
}
# To disable ETags (e.g., behind a load balancer where
# different servers generate different ETags):
location /api/ {
etag off;
add_header Cache-Control "no-cache";
}
Debugging 304 Issues
- Getting 304 when content changed: Check that your server regenerates ETags when content updates. File-based ETags use inode+size+mtime; if your deployment does not update timestamps (e.g., rsync with
--times), ETags may not change. - Never getting 304 (always 200): Verify the server sends ETag or Last-Modified headers. Some frameworks strip caching headers. Also check that no middleware adds
Cache-Control: no-store. - Load balancer ETag mismatches: If multiple servers generate different ETags for the same file (because ETags include the inode number), requests alternating between servers will never match. Either disable inode-based ETags or use a content-based hash.
- Force-bypassing cache: Use
curl -H "Cache-Control: no-cache"or hard-refresh in browser (Ctrl+Shift+R) to skip conditional requests entirely.
Frequently Asked Questions
What triggers a 304 Not Modified response?
A 304 is triggered when the browser sends a conditional request with
If-None-Match (ETag) or If-Modified-Since (timestamp), and the server confirms the resource has not changed. The browser then uses its cached copy. This is automatic — browsers handle conditional requests transparently based on caching headers from previous responses.Why am I getting 304 when content has changed?
This usually means ETags or Last-Modified timestamps are not updating when content changes. Common causes: deployment scripts that preserve file timestamps, weak ETags based on file size (if size did not change), or CDN caching stale ETags. Fix by ensuring your build pipeline generates new fingerprints, or add cache-busting query strings to asset URLs.
Is 304 Not Modified an error?
No. 304 is a success response that optimizes performance. It means your cache is working correctly. The server validated the cached resource and confirmed it is still current, saving the bandwidth of re-transmitting the full response body.