304 Not Modified
When a conditional request confirms the resource has not changed since the client's last cached copy, the server responds with 304 Not Modified. The response carries no message body and instructs the client to reuse its stored representation.
Usage
The 304 Not Modified status code is sent when the request is safe, such as a GET or HEAD request, and the server determines the resource has not changed since the version stored in the client's cache. This conserves bandwidth because the server does not retransmit data the client already holds.
Note
304 Not Modified is not a redirect despite sharing the 3xx status class. Redirects (301, 302, 307, 308) send the client to a different URL via the Location header. 304 tells the client to reuse its cached copy of the same resource, and no Location header is sent. Browser DevTools sometimes group 304 with redirects, which adds to the confusion.
Note
"200 (from disk cache)" in DevTools means the browser served the resource from cache without contacting the server, because the Cache-Control max-age has not expired. 304 means the browser contacted the server to revalidate and the server confirmed the cached copy is current. A cached 200 involves zero network requests, while a 304 involves one round-trip.
The server generates one or more of the following headers in response:
Because the goal of the 304 Not Modified response is to minimize bandwidth, the server omits headers not included in the original request unless they aid the cache-update process.
For conditional GET requests, the relevant directives are If-None-Match and If-Modified-Since. These rely on a validator such as ETag to evaluate whether the resource needs retransmission.
Example
The client requests a resource, receives an ETag identifying the version, then later revalidates. The server recognizes the resource is unchanged and returns 304 Not Modified. On the third request, a new version is available and a full 200 response is sent with an updated ETag.
Note
Each time the server updates a resource, a new ETag is generated. Alternatively, the server returns a Last-Modified date, and the client uses If-Modified-Since instead, removing the need for the server to maintain an ETag history.
SEO impact
When Googlebot receives a 304, the crawler recognizes the content has not changed since the last crawl. The indexing pipeline recalculates ranking signals for the URL but does not re-fetch or re-index the content.
Stale 304 trap
A 304 confirms the cached version is still current. When the server previously served a broken page (empty body, error content) with a 200 and the crawler cached the result, a subsequent 304 tells the crawler the broken version persists. The crawler stops rechecking because the "content" has not changed. Fixing the page alone is not enough. The server must return a fresh 200 with the corrected body (and a new ETag) to replace the cached error. Debugging this scenario is difficult because the root cause (a past transient error) is no longer visible.
Initial request
GET /news.html HTTP/1.1
Host: www.example.re
Initial response
HTTP/1.1 200 OK
ETag: "1234000"
Content-Type: text/html
Content-Length: 1250
<message body contains requested resource>
Second request
GET /news.html HTTP/1.1
Host: www.example.re
If-None-Match: "1234000"
Second response
HTTP/1.1 304 Not Modified
ETag: "1234000"
Third request
GET /news.html HTTP/1.1
Host: www.example.re
If-None-Match: "1234000"
Third response
HTTP/1.1 200 OK
ETag: "1234001"
Content-Type: text/html
Content-Length: 1600
<message body contains requested resource>
Code references
.NET
HttpStatusCode.NotModified
Rust
http::StatusCode::NOT_MODIFIED
Rails
:not_modified
Go
http.StatusNotModified
Symfony
Response::HTTP_NOT_MODIFIED
Python3.5+
http.HTTPStatus.NOT_MODIFIED
Java
java.net.HttpURLConnection.HTTP_NOT_MODIFIED
Apache HttpComponents Core
org.apache.hc.core5.http.HttpStatus.SC_NOT_MODIFIED
Angular
@angular/common/http/HttpStatusCode.NotModified
See also
- RFC 9110: HTTP Semantics
- Google: HTTP status codes and network errors
- 200
- If-Modified-Since
- If-None-Match
- ETag
- Conditional-Requests
- Caching
- HTTP status codes