304 Not Modified
The client's cached copy of the resource is still valid. The server is not returning a response body.
Meaning & Description
This happens when the client performs a conditional GET request. The client sends headers like `If-None-Match` (containing an ETag) or `If-Modified-Since` (containing a date). If the server determines that the resource has not changed since that ETag or date, it returns a 304 without a body, saving bandwidth and processing time. The client then knows it is safe to use its locally cached copy.
Common Causes
- Normal, healthy cache validation between client and server.
- CDNs re-validating stale edge cache with the origin server.
- Browser navigating back to a previously visited page.
How to fix a 304 error
- Ensure you are not accidentally including a response body with the 304 status code (this violates the spec).
- Verify that the `ETag` generated by the server exactly matches the one sent by the client in `If-None-Match`.
- If you are always seeing 200 OKs instead of 304s, check that your server is properly emitting `ETag` or `Last-Modified` headers on the initial request.
- If caching is stuck on a 304 but the resource *has* changed, ensure your ETags are being recalculated properly based on file content or database state.
Browser & SEO Behaviour
Browser Behavior
Upon receiving a 304, the browser immediately stops waiting for a payload, reaches into its internal cache, and uses the locally stored version of the resource exactly as if a 200 OK had been returned.
SEO Impact
Neutral to Positive. 304s speed up crawl budget. Googlebot uses conditional GETs. If your server returns 304s for unchanged pages, Googlebot can crawl more of your site faster, which is excellent for SEO.
CDN Behavior
CDNs heavily rely on 304s. When a cached item at the edge expires, the CDN sends a conditional GET to the origin. If the origin returns 304, the CDN renews the edge cache TTL without having to download the file again.
Code Examples
HTTP/1.1 304 Not Modified
Date: Mon, 12 Sep 2026 12:00:00 GMT
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Cache-Control: max-age=3600
[NO BODY]app.get('/data', (req, res) => {
const currentETag = 'W/"123456"';
// Express handles ETags automatically for res.send()
// But you can manually check conditionals:
if (req.headers['if-none-match'] === currentETag) {
return res.status(304).end();
}
res.set('ETag', currentETag);
res.json({ data: 'Hello World' });
});from fastapi import FastAPI, Request, Response
app = FastAPI()
@app.get("/data")
def get_data(request: Request, response: Response):
current_etag = '"123456"'
if request.headers.get("if-none-match") == current_etag:
return Response(status_code=304)
response.headers["ETag"] = current_etag
return {"data": "Hello World"}func handler(w http.ResponseWriter, r *http.Request) {
currentETag := "\"123456\""
if match := r.Header.Get("If-None-Match"); match == currentETag {
w.WriteHeader(http.StatusNotModified)
return
}
w.Header().Set("ETag", currentETag)
w.Write([]byte("Data"))
}[HttpGet("data")]
public IActionResult GetData([FromHeader(Name = "If-None-Match")] string ifNoneMatch)
{
var currentETag = "\"123456\"";
if (ifNoneMatch == currentETag)
{
return StatusCode(304);
}
Response.Headers.Add("ETag", currentETag);
return Ok(new { data = "Hello World" });
}curl -I -H 'If-None-Match: "33a64df5"' http://example.com/styles.cssRaw HTTP Response Example
HTTP/1.1 304 Not Modified
Date: Mon, 12 Sep 2026 12:00:00 GMT
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Cache-Control: public, max-age=31536000
Real-world Examples
Frequently Asked Questions
Why does a 304 Not Modified response have no body?
Because the entire purpose of the 304 status is to tell the client, "You already have the data, I don't need to send it again." Sending a body would defeat the bandwidth-saving purpose of the status code.
What is an ETag?
An ETag (Entity Tag) is a unique identifier (usually a hash) representing a specific version of a resource. When the resource changes, the ETag changes.
How is If-None-Match different from If-Modified-Since?
If-None-Match relies on ETags (hashes), which are exact and handle byte-level changes perfectly. If-Modified-Since relies on timestamps, which are less precise (usually rounded to the second) and can suffer from server clock sync issues. ETags are preferred.
Do 304s save server CPU?
It depends on your implementation. They always save bandwidth. However, if your server has to query the database and render the entire JSON payload just to calculate the ETag hash, you haven't saved CPU. To save CPU, store the ETag or Last-Modified date alongside the record in the database.
Can I return a 304 for a POST request?
No. The 304 status code is strictly defined for conditional GET or HEAD requests.
Did You Know?
Despite being categorized as a 3xx Redirection code, 304 does not actually redirect the client to a different URL. It redirects the client to its own local cache.
There is a concept called "Weak ETags" (prefixed with W/). They indicate that two resources are semantically equivalent, even if they aren't byte-for-byte identical (e.g., dynamically generated pages with a timestamp footer).
Developer Tips
- Always strip the response body before sending a 304. Most modern web servers and frameworks do this automatically, but if you are writing raw HTTP responses, a body on a 304 violates the spec.
- Use ETags for APIs to save bandwidth, especially for mobile clients polling for updates.
- For static assets, combine ETags with aggressive Cache-Control (e.g., max-age=31536000). When the asset changes, change the filename (e.g., style.v2.css) instead of relying solely on 304s.
Interview Questions
These are questions you might face in a backend or API design interview that touch on HTTP 304.
Explain the flow of a conditional GET request and how a 304 status code is generated.
First, the client fetches a resource, and the server returns a 200 OK with an ETag header. Later, the client requests the resource again, including an If-None-Match header with that ETag. The server compares the client's ETag with the current resource ETag. If they match, the server returns 304 Not Modified with no body.
What happens if a server returns a 304 Not Modified, but includes a response body?
It violates the HTTP specification. Clients and intermediate proxies will likely get confused, potentially corrupting the connection or resulting in parsing errors for subsequent requests on the same connection.
How do 304 Not Modified responses impact API rate limiting?
Well-designed APIs (like the GitHub API) do not count 304 responses against a user's rate limit. This encourages developers to use conditional requests, reducing load on the API's backend infrastructure.
Common Interview Mistakes
- Thinking 304 involves a Location header or URL redirect.
- Not knowing the relationship between 304 and ETags (If-None-Match).
- Failing to mention that 304 responses strictly forbid a response body.