HTTP status codes / 304
304 Not Modified — HTTP Status Code
304 Not Modified means the client's cached copy of a resource is still current, so the server skips sending it again. It only happens in reply to a conditional request, one that includes If-None-Match or If-Modified-Since. It saves bandwidth and time, and it is not an error.
304 Not Modified is the server telling a client "the copy you already have is still good." No body is sent, just headers. The client then displays its cached copy. Most of the 304s in a busy access log are browsers revalidating stylesheets, scripts and images they downloaded on a previous visit.
What 304 means
RFC 9110 (Section 15.4.5) defines 304 as the answer to a conditional GET or HEAD that would have produced a 200 OK if its condition had not evaluated to false. In other words, the client said "send this only if it changed," and it did not change.
Key rules:
- Only for conditional requests. The client must send a validator, normally
If-None-Matchwith anETag(Section 13.1.2), orIf-Modified-Sincewith a date (Section 13.1.3). - No body. A 304 ends at the header section and cannot contain content.
- Some headers are required. If the 200 would have included them, the 304 must carry
Content-Location,Date,ETag,Vary,Cache-ControlandExpires, so caches can refresh their stored metadata. - It is a redirect of sorts. The RFC files it under 3xx because it sends the client to its stored representation instead of to a new URL.
How the exchange works
On the first visit, the server sends the file with a validator:
HTTP/2 200
etag: "5f2c-61a8"
cache-control: no-cache
On a later visit, the browser asks whether that version is still current:
GET /assets/app.css
If-None-Match: "5f2c-61a8"
If the ETag still matches, the server answers:
HTTP/2 304
etag: "5f2c-61a8"
and the browser uses its stored copy. If the file changed, the server sends a normal 200 with the new content and a new ETag.
Cache-Control: no-cache means "revalidate before every use," not "do not cache." It is a common way to make sure users see updates quickly while still benefiting from 304s.
When you see it
- Browsers revalidating cached pages and assets.
- CDNs and reverse proxies revalidating their cached copies with the origin, and answering conditional requests from browsers on the origin's behalf.
- API clients and feed readers that store ETags to avoid downloading unchanged data.
- Package managers and sync tools checking whether a file changed.
Problems around 304
- Stale content after deploys. If a build reuses the same ETag for changed files, or the server derives
Last-Modifiedfrom a timestamp that a deploy does not update, clients keep receiving 304 for outdated files. Fingerprinted file names, such asapp.3f9a1c.css, sidestep the issue. - ETags that differ between servers. Behind a load balancer, servers that compute ETags differently (for example from inode numbers) produce mismatches, so clients download the full file more often than necessary. Configure a consistent ETag scheme.
- Compression changing validators. Some servers alter the ETag of compressed responses. That is correct behavior, but it can make revalidation look broken when comparing headers by hand.
How to check it
First, grab the validator:
curl -sI https://www.example.com/assets/app.css | grep -iE "^(etag|last-modified)"
Then send it back as a condition:
curl -sI -H 'If-None-Match: "5f2c-61a8"' https://www.example.com/assets/app.css
A server that supports revalidation answers HTTP/2 304. If you get 200 again, the ETag changed, the server does not support conditional requests for that file, or something in between strips the header.
What it means for uptime monitoring
A monitor will not normally see 304, because 304 only answers conditional requests and PingRobot's checker does not send any: its GET carries no If-None-Match or If-Modified-Since header. Each check therefore asks the server for the full response, which is what you want when testing whether the site works.
If a 304 ever were the final status, it would count as up, since 200 to 399 is treated as up. But a 304 has no body, so a keyword monitor would have nothing to search. That situation would point to an unusual server or proxy setup sending 304 without being asked, which is worth investigating on its own.
Related codes
- 200 OK — the full response a 304 stands in for
- 206 Partial Content — another response shaped by request headers and validators
- 204 No Content — the other common success with an empty body
- 301 Moved Permanently — a 3xx that does point to a new URL
Start monitoring free — 10 monitors, no credit card.
Frequently asked questions
Is 304 Not Modified an error?
No. It means the client already has the latest version in its cache. The browser then uses the cached copy as if the server had sent a 200 with the same content.
Why is 304 listed with redirects?
RFC 9110 groups it under 3xx because it redirects the client to a stored copy rather than to another URL. There is no Location header and no new address.
Why do I see 304 in my server logs but not in curl?
Browsers send conditional headers automatically when they have a cached copy. A plain curl request does not, so it receives a full 200. Add If-None-Match or If-Modified-Since to reproduce the 304.
Why do users still get an old version of a file after a deploy?
A 304 only confirms what the validators say. If the ETag or Last-Modified value did not change when the file did, or the browser is still within the cache lifetime and does not ask at all, it keeps the old copy. Versioned file names avoid this.