HTTP

HTTP 304Not Modified

HTTP 304 Not Modified is the server's answer to a conditional request: the client sent If-None-Match or If-Modified-Since with its cached copy's validator, and the resource has not changed, so the server sends no body and the client reuses its cache. It is normal, healthy behaviour, not an error.

BlackhawkHub Editorial · Updated

How a 304 happens

  1. The client fetches a resource and receives 200 OK with a validator: ETag: "abc123" and/or Last-Modified: Tue, 15 Sep 2026 10:00:00 GMT.
  2. Later, when its cached copy is stale (per Cache-Control: max-age) or the user reloads, the client asks again with If-None-Match: "abc123" or If-Modified-Since: ….
  3. The server compares. Unchanged → 304 Not Modified with no body, and the client keeps using its copy. Changed → 200 OK with the new content and a new validator.

The cost of a 304 is one small round trip; the saving is the entire body.

What the client can rely on

A 304 may include updated Cache-Control, Expires, ETag and Vary headers, which the client applies to its stored response. It must not include a body.

Configuring validators

Static files: Apache and nginx generate ETag and Last-Modified automatically. Apache's default ETag once included the inode, which broke across servers; FileETag MTime Size avoids that.

Dynamic responses: the application must compute an ETag (a hash of the output or a version number) and compare it to If-None-Match before rendering, otherwise the server does all the work and only saves transfer.

Testing

cmd
curl -I https://example.com/style.css
curl -I -H "If-None-Match: \"abc123\"" https://example.com/style.css

The first shows the ETag; the second, with that ETag supplied, should return 304 Not Modified. Timestamps for If-Modified-Since must be in HTTP date format; the Unix timestamp converter converts epoch values from logs.

Frequently asked questions

Is 304 an error?

No. Browser developer tools show many 304s on a reload; each one is a resource the browser already had and did not need to download again.

Why does my site never return 304?

The server is not sending validators (ETag or Last-Modified), or a proxy strips them, or the client is not sending conditional headers. Check the response headers of a 200 first.

Should I use ETag or Last-Modified?

Both if you can. ETag is precise (content hash or version); Last-Modified has one-second granularity. When both are present, clients send both and the server checks ETag first.

Sources