HTTP 304 Not Modified: what it means and when it is a problem
DevTools shows a column of 304s and you wonder whether something is broken. Usually it is the opposite: the browser asked “has this changed?”, the server said “no”, and nothing had to be downloaded again.
What the exchange looks like
GET /logo.png HTTP/1.1
If-None-Match: "a1b2c3"
If-Modified-Since: Fri, 06 Nov 2026 10:00:00 GMT
HTTP/1.1 304 Not Modified
ETag: "a1b2c3"
Cache-Control: max-age=600
Try it yourself with curl: copy the ETag from a normal request and send it back.
curl -sI https://example.com/logo.png | grep -i etag
curl -sI https://example.com/logo.png -H 'If-None-Match: "a1b2c3"' # expect 304
304 vs 200 “from cache”
- 200 (from memory/disk cache): the copy was still fresh (within
max-age), so the browser did not contact the server at all. - 304: the copy had expired (or was marked
no-cache), so the browser asked, and the server confirmed it was still current. One small round trip, no body. - 200 with a body: the file changed (or the browser had no copy), so it was downloaded again.
The Wirebook browser-cache lesson lets you play this out with a pretend server and a shelf of saved copies.
When a 304 is a real problem
- Content changed but the ETag did not. A hand-built ETag based on something that did not change (a version constant, the file size) makes the server say “not modified” forever. Derive ETags from the content hash or modification time.
- Several servers, different ETags. Behind a load balancer, inode-based ETags (old Apache defaults) differ per machine, so you get needless 200s. Not harmful, just wasteful.
- API clients that ignore 304. A
fetchin the browser handles it for you, but a custom client that sendsIf-None-Matchmust handle an empty 304 body and reuse its own saved copy. - Compression changes the ETag. Some servers turn a strong ETag into a weak one (
W/"…") when gzipping. Weak ETags are fine for If-None-Match.
Turning it off (usually unnecessary)
To force full downloads while debugging, tick Disable cache in DevTools. On the server, removing ETag and Last-Modified stops 304s, but makes every repeat visit heavier. If the real problem is old CSS or JS after a deploy, fix the URL instead: see old CSS after a deploy.
Related
Range returns 200, not 206 (If-Range) · no-cache vs no-store · Old CSS after a deploy · HTTP 206 Partial Content · HTTP status API
Questions
What does HTTP 304 Not Modified mean?
The browser asked whether its saved copy was still current, and the server said yes, so it sent no body and the saved copy was used.
Is a 304 response an error?
No. It is a normal, successful reply that saves downloading the same file again.
What is the difference between ETag and Last-Modified?
An ETag is a label for one exact version of a file, compared with If-None-Match. Last-Modified is a timestamp, compared with If-Modified-Since. ETags are more precise and win when both are sent.
Why do I see 304 instead of 200 from cache?
The saved copy had passed its max-age (or was marked no-cache), so the browser had to check with the server before using it.
Does a 304 response have a body?
No. A 304 never carries a body; it can carry updated headers such as Cache-Control and ETag.
Published · updated .