304 Not Modified: conditional requests and crawl efficiency
304 Not Modified tells a client its cached copy is still current, so the server sends headers only and no body.
304 Not Modified is the server’s way of saying “you already have the latest version.” It only appears in reply to a conditional request, and it has no body, which saves bandwidth for browsers, CDNs and crawlers alike.
How conditional requests work
RFC 9110, section 15.4.5 defines 304 as the answer to a conditional GET or HEAD that would have returned 200 OK if the condition hadn’t evaluated to false. The client builds that condition from validators the server sent earlier:
ETag: the client later sendsIf-None-Matchwith that value.Last-Modified: the client later sendsIf-Modified-Sincewith that date.
If the validator still matches, the server returns 304 with headers only. It must include the headers a 200 would have carried among ETag, Date, Vary, Content-Location, Cache-Control and Expires. When a request has both conditions, If-None-Match wins and If-Modified-Since is ignored.
Why 304 matters for SEO
Google’s documentation on HTTP status codes says a 304 tells the indexing pipeline the content is the same as at the last crawl. Google may recalculate signals for the URL, but otherwise the code has no effect on indexing. In other words, it’s safe, and it’s efficient. Google’s crawlers support both ETag and Last-Modified validation, and Google has recommended ETag as the less error-prone of the two.
The benefit is crawl efficiency. A 304 costs your server almost nothing to send, so Googlebot can check more URLs with the same resources. On large sites with many unchanged pages, that means faster discovery of the pages that did change.
Where 304 goes wrong
- Stale validators. If the ETag is computed from something that doesn’t change when the content does, such as a template version, the server keeps answering 304 after you update the page, and caches keep the old copy.
- Last-Modified not updated when comments, prices or related links change the HTML.
- 304 to unconditional requests. A buggy cache layer that sends 304 to a client that didn’t ask conditionally leaves it with nothing to show.
- ETags altered by compression. Some servers modify or weaken ETags when they compress responses, so the next
If-None-Matchnever matches. - Different ETags per server behind a load balancer, so consecutive requests rarely match.
How to enable and test it
Apache and Nginx usually add validators to static files automatically (etag on; is Nginx’s default). Dynamic HTML often lacks them; a page cache, CDN or your framework’s conditional-response middleware can add them. A simplified version in PHP:
$etag = '"' . md5($html) . '"';
header('ETag: ' . $etag);
if (trim($_SERVER['HTTP_IF_NONE_MATCH'] ?? '') === $etag) {
http_response_code(304);
exit;
}
echo $html;
First confirm the page sends an ETag or Last-Modified header with the HTTP header checker. Then replay the validator with curl and expect a 304:
curl -I https://www.example.com/guide/ \
-H 'If-None-Match: "6a1f-5e2c"'
HTTP/2 304
etag: "6a1f-5e2c"
cache-control: public, max-age=600
date: Fri, 30 Oct 2026 08:00:00 GMT
The page speed test also reviews your caching headers.
Frequently asked questions
Does a 304 Not Modified response hurt SEO?
No. Google says a 304 simply tells it the content hasn’t changed since the last crawl. It saves resources and can make crawling of large sites more efficient.
Does Googlebot send If-None-Match or If-Modified-Since?
Google’s crawlers support conditional requests with ETag and If-None-Match as well as Last-Modified and If-Modified-Since. Whether a particular fetch is conditional depends on Google having a previous copy of the URL.
Why does DevTools show 304 while a checker shows 200?
Your browser had a cached copy and sent validators, so the server answered 304. A checker requests the page fresh without validators and gets 200. Both are correct.