304 Not Modified
The cached version of the resource is still valid, so the server tells the client to keep using it.
Defined in RFC 9110 §15.4.5
What 304 means
A 304 response is sent when a conditional GET or HEAD request indicates the client already has a fresh copy of the resource. Instead of resending the full body, the server confirms the cached representation is still valid, and the client reuses what it already stored. This is a pure caching optimization: it saves bandwidth and server processing while keeping the client’s view of the resource up to date, and RFC 9110 requires the response carry no body.
Conditional requests are driven by two independent mechanisms that can be used together. The strong mechanism uses ETag: the server sends an ETag header on the full response, the client echoes it back as If-None-Match on the next request, and the server returns 304 if the current ETag still matches. The weaker, time-based mechanism uses Last-Modified paired with If-Modified-Since: the client sends the timestamp it last saw, and the server compares it to the resource’s current modification time. ETag is preferred where available because it detects any content change, including ones that happen within the same second, which Last-Modified’s one-second resolution can miss.
A 304 response must still include headers that would have changed between requests, such as Cache-Control, ETag, Vary, and Expires, so the client can update its cache metadata even though the body is not resent. Getting this wrong is a common bug: a server that regenerates ETags inconsistently (for example, based on a timestamp that differs per process in a multi-server deployment) will rarely or never emit 304, defeating the point of conditional caching and wasting bandwidth on redundant transfers.
304 is central to how CDNs, reverse proxies, and browsers minimize origin load for static assets, API responses, and images. Misconfigured conditional headers are a frequent source of subtle bugs: stale content served indefinitely because validators never change, or the opposite, needless full re-downloads because validators change on every request even when content is identical.
Common causes
- A browser or HTTP client sends If-None-Match with an ETag that still matches the current resource.
- A client sends If-Modified-Since with a timestamp equal to or later than the resource’s last modification time.
- A CDN edge node revalidates a cached object with the origin and the origin confirms it is unchanged.
- A static asset pipeline serves the same file (and thus the same ETag) across repeated builds with no content change.
- An API endpoint implements conditional GET support to reduce payload size for polling clients.
How to fix a 304
If you are the client (browser user or API caller)
- Send If-None-Match (preferred) or If-Modified-Since on repeat requests for a resource you have already cached, using the values from the prior response’s ETag or Last-Modified.
- On receiving 304, reuse the previously cached body and headers rather than treating the empty response as an error or missing content.
- Ensure your HTTP client or fetch wrapper does not strip conditional request headers when replaying cached requests.
- If you always get 200 instead of expected 304s, verify you are actually storing and resending the validator from the prior response.
If you run the server
- Generate stable, deterministic ETags (e.g., a content hash) so identical content produces the same validator across servers and deployments.
- Compare incoming If-None-Match and If-Modified-Since headers correctly and return 304 with no body when the resource is unchanged.
- Always resend Cache-Control, ETag, and Vary on the 304 response itself, since these may have changed even though the body has not.
- For load-balanced deployments, share or synchronize ETag/Last-Modified generation across instances so revalidation is consistent regardless of which server handles the request.
- Audit CDN and reverse-proxy revalidation rules so cached objects are actually being conditionally checked against the origin rather than always refetched or never refetched.
Example
GET /styles.css HTTP/1.1
Host: example.com
If-None-Match: "33a64df551"
HTTP/1.1 304 Not Modified
ETag: "33a64df551"
Cache-Control: max-age=3600Try it live
Our free status responder returns a real HTTP 304 you can point tests, monitors or a browser at.
GET https://mcp.httpstatus.com/status/304