304 Not Modified
The HTTP 304 Not Modified status code is returned by the server to indicate a conditional request succeeded but the resource has not changed since the client's last cached copy. 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.
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
Takeaway
The 304 Not Modified status code indicates the request succeeded but the client already holds the current version in its cache. The server conserves bandwidth by responding without a body, instructing the client to reuse its stored copy.
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