How HTTP Works

Comparison

304 Not Modified vs 200 OK

Understand when servers return 304 Not Modified instead of 200 OK. Learn how conditional requests, ETags, and Last-Modified headers enable efficient HTTP caching.

Bottom line: Use 200 when the server needs to send the resource body. Use 304 when the client already has a cached copy and only needs confirmation that it is still current.

By How HTTP WorksReview process
304 Not Modified
vs
200 OK

TL;DR: Use 200 when the server needs to send the resource body. Use 304 when the client already has a cached copy and only needs confirmation that it is still current.

The Fast Mental Model

If you are looking at DevTools and wondering why the browser got “no real response body,” the answer is usually this:

304 Not Modified is not a failure and it is not an empty 200. It is a successful cache revalidation response.

The Core Difference

200 OK is the baseline response. The server sends headers and a full body.

304 Not Modified only appears when the client asks a conditional caching question, such as “has this changed since the version with this ETag?” If the answer is no, the server skips the body and the client reuses its cached content.

Why You See 304 In Real Life

You usually encounter 304 in one of these situations:

304 only happens when the client sends a conditional request:

# First request — client has nothing cached
GET /styles.css HTTP/1.1

# Server responds with full content + cache validators
HTTP/1.1 200 OK
ETag: "abc123"
Cache-Control: max-age=3600
[full CSS body]

# Later — cache has expired, client revalidates
GET /styles.css HTTP/1.1
If-None-Match: "abc123"

# Resource unchanged — server sends 304, no body
HTTP/1.1 304 Not Modified
ETag: "abc123"
Cache-Control: max-age=3600

Comparison Table

200 OK304 Not Modified
Response bodyYes (full resource)No
Triggered byAny GET/HEADConditional GET (If-None-Match / If-Modified-Since)
Client actionStore in cacheUse existing cached copy
Bandwidth usedFull resource sizeHeaders only (~200–500 bytes)
Requires prior requestNoYes (needs cached ETag or Last-Modified)

ETag vs Last-Modified

Servers can use two mechanisms to enable conditional requests:

ETag — a content fingerprint, usually a hash of the response body:

ETag: "d41d8cd98f00b204e9800998ecf8427e"

Client sends back as If-None-Match: "d41d8cd98f00b204e9800998ecf8427e".

Last-Modified — a timestamp of when the resource last changed:

Last-Modified: Tue, 18 Feb 2026 00:00:00 GMT

Client sends back as If-Modified-Since: Tue, 18 Feb 2026 00:00:00 GMT.

ETags are more reliable. A file can be regenerated with identical content (same ETag, different timestamp) or have its mtime updated without content changes. Use ETags when your server can compute them cheaply.

Cache-Control Interaction

304 only comes into play after a cache entry expires. The flow is:

  1. Fresh cache (max-age not exceeded) — browser uses cached copy directly, no request sent
  2. Stale cache (max-age exceeded) — browser sends conditional request → server returns 304 or 200
  3. No cache validators (no ETag, no Last-Modified) — browser must fetch full 200

Cache-Control: no-cache forces revalidation on every request (step 2 always), but still allows 304 if the server supports it. Cache-Control: no-store disables caching entirely — always 200.

Common Mistakes

Treating 304 like a missing response — it is not missing anything. The response is telling the client that the cached body is still authoritative.

Not sending ETag or Last-Modified — without validators, the browser cannot revalidate efficiently and must fall back to full 200 responses.

Confusing 304 with 204 — 304 means “reuse your cached copy.” 204 No Content means “the request succeeded, and there is no body to send for this operation.”

Expecting 304 for POST — conditional cache revalidation is for GET and HEAD, not for write-oriented methods.

FAQ

Does a 304 response have a body?

No. A 304 response must not include a message body. The entire point is that the client already has the body in its cache. The server only sends headers — typically Cache-Control, ETag, and Vary — to update the cached entry metadata.

What triggers a 304 response?

A conditional request triggers a 304. The client sends If-None-Match (with a cached ETag value) or If-Modified-Since (with a cached date). The server compares these against the current resource. If nothing has changed, it returns 304. If the resource has changed, it returns 200 with the new content.

What is the difference between ETag and Last-Modified for cache validation?

ETag is a content fingerprint (usually a hash) — it changes whenever the content changes, regardless of time. Last-Modified is a timestamp. ETags are more reliable: a file can be modified and restored to its original content (same ETag, different timestamp) or have its timestamp updated without content changes (same content, different timestamp). Prefer ETags when possible.

Can I get a 304 for a POST request?

No. A 304 is only returned for GET and HEAD requests that carry If-None-Match or If-Modified-Since. For POST, PUT, PATCH and DELETE, a failed precondition (If-Match, If-Unmodified-Since) produces 412 Precondition Failed instead (RFC 9110 section 13.1).

Why does my browser sometimes get 200 even when the resource has not changed?

Several reasons: the server may not implement ETags or Last-Modified headers; the Cache-Control header may have no-cache (which forces revalidation but the server still returns 200 if it does not support conditional requests); or the cache entry may have expired and the browser is making a fresh request rather than a conditional one. Check that your server sends ETag or Last-Modified on the initial 200 response.

References

Browse /search