What 304 means
The client already has a valid cached copy, so the server sends no body. 304 is defined in RFC 9110 §15.4.5 and is the answer to a conditional request (§13). A browser that cached a response with validators:
HTTP/1.1 200 OK
ETag: "33a64df5"
Last-Modified: Tue, 15 Sep 2026 08:00:00 GMT
Cache-Control: no-cache
asks again with those validators when its copy is stale or must be revalidated:
GET /styles.css HTTP/1.1
If-None-Match: "33a64df5"
If-Modified-Since: Tue, 15 Sep 2026 08:00:00 GMT
and if nothing changed the server answers:
HTTP/1.1 304 Not Modified
ETag: "33a64df5"
Cache-Control: no-cache
The client then reuses the body it already has. A 304 must not have a body, and it must include the headers a 200 would have carried among Cache-Control, Content-Location, Date, ETag, Expires and Vary, so the cache can update its stored copy.
ETag or Last-Modified
ETag(§8.8.3) is an opaque version tag. Strong ETags ("abc") promise byte-identical content; weak ones (W/"abc") mean equivalent content.If-None-Matchuses weak comparison, so both work for 304s.Last-Modified(§8.8.2) is a date with one-second resolution, checked withIf-Modified-Since.- When a request has both,
If-None-Matchwins andIf-Modified-Sinceis ignored.
Why you don't get 304s
- No validators: dynamic pages without
ETagorLast-Modified, so there's nothing to compare. - ETags that differ per server behind a load balancer (old Apache setups that included the file's inode), so every revalidation misses.
- Compression changing the ETag: nginx turns strong ETags into weak ones when it gzips; Apache's mod_deflate appends
-gzip, which older versions then failed to match. - Frameworks that rebuild the page and compare nothing. Express sends weak ETags for
res.sendbodies and answers 304 automatically; elsewhere you may need middleware. - A 304 to unconditional requests. Only answer 304 when the request had
If-None-MatchorIf-Modified-Since; otherwise the client has no copy to reuse.
In browser DevTools, "304" means the browser asked and was told to reuse its copy; "(memory cache)" or "(disk cache)" with 200 means it didn't need to ask at all.
304 and SEO
Google's crawlers support HTTP caching: they send If-None-Match and If-Modified-Since when they have a previous copy, and Google has said it prefers ETag. Per Google's documentation, a 304 tells the indexing pipeline the content is the same as last time; signals may be recalculated, but the status otherwise has no effect on indexing. Accurate 304s save bandwidth and crawl budget on large sites. Only send them when the content really hasn't changed, or Google won't see your updates.
Related codes
- 200 OK: the full response with a fresh body.
- 206 Partial Content: the other response that relies on
ETag(viaIf-Range). - 412 Precondition Failed: the conditional answer for
If-Matchon writes.
Send a request with your own If-None-Match header and check the status with the HTTP Request Builder.