3xx Redirection · RFC 9110 §15.4.5

304 Not Modified

The cached copy is still valid; no body is sent.

What 304 means

Sent in reply to a conditional request (If-None-Match or If-Modified-Since) when the resource has not changed. The client reuses its cached copy, saving bandwidth.

Lots of 304s in a log are a sign that caching works. A 304 carries no body, so a client that tries to parse one gets nothing.

Common causes

  • A browser or CDN revalidated a cached asset and the ETag or Last-Modified matched.

How to fix it

  • If assets never return 304 and always 200, the server is not sending ETag or Last-Modified headers, or Cache-Control: no-store disables caching.

What it looks like

A typical response:

HTTP/1.1 304 Not Modified
ETag: "5f3a-1a2b3c"
Cache-Control: public, max-age=3600

The same event in an nginx access log (the status is the number after the request line):

203.0.113.7 - - [10/Sep/2026:10:12:01 +0000] "GET /static/app.css HTTP/1.1" 304 0 "-" "Mozilla/5.0"

Check it with curl

-i prints the status line and headers, and -w '%{http_code}' prints only the number, which is handy in scripts and health checks. Replace the URL with yours:

curl -i -H 'If-None-Match: "5f3a-1a2b3c"' https://example.com/static/app.css

Compare what curl sees with what the browser sees. A different status from the same URL usually means a cache, a CDN edge or a cookie is in the way.

Investigating a run of 304s? Paste the log excerpt into Log Share to get line numbers, highlighting and an expiring link for whoever is on call with you.

  • 200OK: The request succeeded and the response carries the result.
  • 412Precondition Failed: an If-Match or If-Unmodified-Since condition was false.

FAQ

Why does my API return 304 to fetch() and the body is empty?

The browser sent a conditional request and the server said the copy is current. fetch() transparently returns the cached body as a 200 to your code; if you see 304 directly you bypassed the cache layer. Add Cache-Control: no-store for dynamic API responses.