304 Not Modified

What HTTP 304 Not Modified means, why it's a 3xx code but not a redirect, how ETag and Last-Modified drive it, and why it can indirectly help crawl efficiency on large sites without affecting rankings.

First published: Jul 3, 2026 · Last updated: Jul 17, 2026 · Advanced
demand #16 in HTTP Status Codes#132 in Technical SEO#181 on the site
1 evidence signal on this page

HTTP 304 Not Modified is the response to a conditional GET or HEAD request whose condition evaluates false — one that would otherwise have gotten a 200. It's a 3xx-class response that is not a redirect — it has no Location header and, by spec, no body at all. A GET/HEAD carrying If-None-Match (matched against an ETag) or If-Modified-Since (matched against Last-Modified) is what makes a request conditional in the first place; a second visit is the typical example, not the protocol rule. When the validator still matches, the server replies 304 with no body, and the client reuses its cached copy. For SEO there's no direct ranking effect — Google already has the content, and a 304 just confirms nothing changed, though Search may still recalculate a URL's signals. The real payoff is resource savings: on large sites with many rarely-changing URLs, 304s let crawlers skip re-downloading unchanged pages, saving bandwidth and server compute, which Google says may indirectly improve crawl efficiency — it doesn't promise an automatic reallocation of crawl budget to other URLs. Google recommends ETag as the primary validator (it has no date-formatting pitfalls), supports setting both, and treats a change as significant enough to invalidate cache only when the content actually changes — not a footer copyright year. Don't confuse 304 with 301/302/307/308 (which move you to a different URL) or with 204 No Content (also bodyless, but because there's genuinely nothing to send).

TL;DR — 304 is the response to a conditional GET/HEAD whose condition evaluates false and that would otherwise have gotten a 200 (RFC 9110 §15.4.5). It’s 3xx-class but not a redirectA redirect sends browsers and crawlers from a requested URL to a different one. An HTTP redirect specifically is a 3xx status code paired with a Location header; meta refresh and JavaScript redirects achieve a similar navigation without being a 3xx response themselves. Permanent redirects (301/308) are Google's signal the target should be canonical; temporary ones (302/303/307) aren't.: no Location, no new URL, and — normatively — no body (“it cannot contain content or trailers”). The condition is carried by If-None-Match (vs. an ETag) and/or If-Modified-Since (vs. Last-Modified); if the validator still matches, the server returns 304 and the client reuses its cache. SEO impact: no direct ranking effect — Google already has the content, though Search may still recalculate a URL’s signals — and a resource-savings win on large sites (Google says it may indirectly improve crawl efficiency; it doesn’t promise automatic reallocation of crawl budget to other URLs). Google’s crawlingCrawling is how search engines use automated bots (like Googlebot and Bingbot) to discover URLs and download pages. A page has to be crawlable to be indexed, but crawling on its own isn't a ranking factor. infrastructure supports both validators and recommends ETag as primary (it has no date-formatting pitfalls). Contrast it with 301/302/307/308 (which move the URL) and 204 (also bodyless, but because there’s genuinely nothing to send).

What 304 means in the spec

RFC 9110 (HTTP Semantics) §15.4.5 defines it precisely: “The 304 (Not Modified) status code indicates that a conditional GET or HEAD request has been received and would have resulted in a 200 (OK) response if it were not for the fact that the condition evaluated to false.” That’s the actual, normative trigger — a conditional GET/HEAD whose condition evaluates false and that otherwise would have gotten a 200. In plain terms: the client asked “give me this page, but only if it changed,” the server determined it didn’t change, so it skips sending the body. A “second visit” is the everyday example of how a request becomes conditional (the client attaches a validator it saved from an earlier response), but it’s a teaching example, not the rule itself — nothing in the spec requires a prior visit, just a conditional request. Evidence for this claim RFC 9110 defines 304 Not Modified as the response to a conditional GET or HEAD when the selected representation has not changed and says the response cannot contain content. Scope: HTTP semantics for 304 conditional responses. Confidence: high · Verified: IETF: RFC 9110 §15.4.5 — 304 Not Modified

The spec even uses the word “redirecting” in this section — “the server is therefore redirecting the client to make use of that stored representation as if it were the content of a 200 (OK) response” — but read it carefully: it’s redirecting the client back to its own cache, not to another URL. There’s no Location header and no new address. This single sentence is the source of most of the confusion about whether 304 “is a redirect.” It isn’t, in the HTTP sense.

Two more normative points matter:

  • No body, ever. RFC 9110: “A 304 response is terminated by the end of the header section; it cannot contain content or trailers.” A 304 that ships a body is a spec violation, and some clients will mishandle it. This is a hard rule, not a style preference.
  • It carries the same metadata a 200 would. The server “MUST generate any of the following header fields that would have been sent in a 200 (OK) response to the same request: Content-Location, Date, ETagA conditional request lets a crawler (or browser) ask a server 'has this changed since I last fetched it?' — using If-Modified-Since (checked against your Last-Modified header) and/or If-None-Match (checked against your ETag). If nothing changed, the server replies 304 Not Modified with no body, so the crawler reuses its existing copy instead of re-downloading the page., and Vary,” plus Cache-Control and Expires where applicable. So a 304 is the 200’s headers without the payload.

How 304 actually works: conditional requests

A server never sends a 304 out of the blue. It’s always a response to a conditional request — a request the client makes conditional by attaching a validator it saved from a previous response. There are two validators.

ETag and If-None-Match

An ETag (entity tag) is an opaque tokenA token is the smallest unit of text (or image/audio/video) an LLM processes — roughly 4 characters, or about ¾ of an English word. A context window is the maximum number of tokens (input plus output) a model can hold at once, like its short-term memory. the server attaches to a 200 response — think of it as a version fingerprint for that exact representation of the URL. On the next request for that URL, the client sends the saved value back in an If-None-Match header. The server compares: if the current ETag still matches, nothing changed, so it returns 304; if it differs, it returns a fresh 200 with the new content and a new ETag.

Last-Modified and If-Modified-Since

The date-based alternative: the server sends a Last-Modified timestamp on the 200. Next time, the client sends it back in an If-Modified-Since header, and the server compares dates — if the resource hasn’t changed since that timestamp, it’s a 304. This is simpler but coarser (only as granular as the timestamp) and sensitive to exact HTTP-date formatting, which is a common source of bugs. When both validators are present, If-None-Match (the ETag) takes precedence over If-Modified-Since.

Strong vs. weak ETags

An ETag can be strong or weak, and the difference matters. Per MDN’s conditional-requests guidance, “strong validation consists of guaranteeing that the resource is, byte to byte, identical to the one it is compared to.” A weak ETag is prefixed with W/ (for example, ETag: W/"abc123") and asserts only semantic equivalence — MDN’s example is that “a page that would differ from another only by a different date in its footer, or different advertising, would be considered identical to the other with weak validation.” Strong ETags (no prefix) are required for things like range requests that need byte-exact matching; weak ETags are useful when compressionCompression (HTTP content encoding) shrinks text-based responses — HTML, CSS, JS, JSON, SVG, XML sitemaps — before they're sent over the network, using an algorithm like Gzip, Brotli, or Zstd, so the browser or crawler downloads fewer bytes. It's not a ranking factor, but it speeds up page loads and helps pages stay under crawler fetch limits., whitespace, or trivial non-substantive differences shouldn’t force a full re-fetch. The practical trap: if your ETag changes every time content is gzip/Brotli-recompressed, or varies across load-balanced servers, you’ll trigger unnecessary re-crawls — pick strong vs. weak deliberately and keep the value stable for genuinely-unchanged content.

The full handshake, step by step

  1. First request → server returns 200 OK with the content, plus ETag and/or Last-Modified.
  2. Client stores the content and those validators.
  3. Next request → client sends If-None-Match and/or If-Modified-Since with the stored values.
  4. Server decides: unchanged → 304 Not Modified, no body, the client reuses its cache; changed → 200 OK with the new body and fresh validators. Evidence for this claim RFC 9111 defines validation as checking whether a stored response remains current, typically with a conditional request that can receive 304 Not Modified. Scope: HTTP cache validation; it does not create a Search ranking benefit. Confidence: high · Verified: IETF: RFC 9111 §4.3 — Validation

If you want the deeper treatment of cachingCaching stores a copy of a page or resource — in a browser, a CDN edge node, or a search crawler's own cache — so it can be served again without regenerating or re-downloading it. It isn't a direct ranking factor, but it feeds page speed and crawl efficiency. and validators as a crawl-efficiency lever — the whole conditional requestsA conditional request lets a crawler (or browser) ask a server 'has this changed since I last fetched it?' — using If-Modified-Since (checked against your Last-Modified header) and/or If-None-Match (checked against your ETag). If nothing changed, the server replies 304 Not Modified with no body, so the crawler reuses its existing copy instead of re-downloading the page. story and how it ties into crawl budgetThe number of URLs an engine will crawl in a timeframe. — that’s a companion topic; this article is the status-code-focused view.

304 and SEO: no ranking effect, real crawl-efficiency effect

Here’s the whole SEO story, and it’s narrower than a lot of blog boilerplate implies. 304 has no direct ranking effect, and Google’s own guidance on how status codes affect crawlingCrawling is how search engines use automated bots (like Googlebot and Bingbot) to discover URLs and download pages. A page has to be crawlable to be indexed, but crawling on its own isn't a ranking factor. and indexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. bounds the indexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. effect too: Search may still recalculate a URL’s signals, but otherwise a 304 doesn’t change how the page is indexed. Google already has the content from the previous crawl; a 304 just confirms nothing changed, so it keeps using what it has. There is no ranking bonus for returning 304s.

What 304 does buy you is resource savings that can indirectly improve crawl efficiency. In Google’s December 2024 Search Central blog post on HTTP caching, Gary Illyes put it plainly: “Especially if you have a large site with rarely-changing content under individual URLs, allowing caching locally may help your site be crawled more efficiently. Google’s crawling infrastructure supports heuristic HTTP caching as defined by the HTTP caching standard, specifically through the ETag response- and If-None-Match request header, and the Last-Modified response- and If-Modified-Since request header.”

On the exact 304 mechanism, the same post is explicit about why the empty body is the point: if the ETag the crawlerA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. sends “matches the current value the server generated, your server should return an HTTP 304 (Not modified) status code with no HTTP body,” and that “no HTTP body” part matters because “your server doesn’t have to spend compute resources on actually generating content” and “doesn’t have to transfer the HTTP body” — you save compute and bandwidth on both ends. Google’s own wording for the downstream benefit is conditional: those resource savings may indirectly improve crawl efficiency. It’s not a promise that the saved effort gets automatically reallocated to your new or updated URLs — treat it as a resource-savings mechanism with a plausible, not guaranteed, spillover benefit.

Why this matters more on large sites

If you have a few hundred pages, this is largely academic — Google will crawl your whole site comfortably regardless. The payoff scales with size: a site with hundreds of thousands or millions of URLs, many of them rarely changing, benefits materially when crawlersA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. can skip re-fetching all the unchanged ones. That’s the audience Google’s post is aimed at, and it’s the honest framing to keep — don’t oversell 304 as a small-site tactic.

ETag or Last-Modified, and what counts as “changed”

Google recommends ETag as the primary validator: “We strongly recommend using ETag because it’s less prone to errors and mistakes (the value is not structured unlike the Last-Modified value).” Setting both is fine and encouraged. If you do use Last-Modified, the date “must be formatted according to the HTTP standard” — Google recommends the format “Weekday, DD Mon YYYY HH:MM:SS Timezone,” e.g. “Fri, 4 Sep 1998 19:15:56 GMT” — or it may be silently ignored. Google also suggests setting the max-age field of Cache-Control to help crawlers decide when to recrawl.

What counts as a change worth invalidating the cache for is your call, and Google’s advice is to reserve it for substantive changes: “Our recommendation is that you require a cache refresh on significant changes to your content; if you only updated the copyright date at the bottom of your page, that’s probably not significant.”

What Googlebot and Bingbot actually do with it

Not every crawler request is conditional. Google’s crawler documentation notes that individual Google crawlers and fetchers vary in caching support depending on the product they serve — GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer. supports caching when re-crawling URLs for Search, while some other Google fetchers only support it in certain conditions — so even with headers configured correctly, don’t expect 100% of requests to carry If-None-Match/If-Modified-Since. For Bing: conditional requests aren’t a Google-only feature. Bing’s crawler has supported conditional GET (sending If-Modified-Since and, when available, If-None-Match, and accepting a 304 when content is unchanged) since at least a 2008 Live Search blog post — though Bing hasn’t published a modern equivalent to Google’s 2024 write-up. Treat the mechanics as generic HTTP behavior that applies to both engines.

How to implement 304 support

  1. Send validators on the 200. Configure your server, CDN, or app to attach an ETag (recommended) and/or a correctly formatted Last-Modified header to normal 200 responses. Many servers and frameworks do ETags automatically for static files; dynamic responses usually need you to opt in.
  2. Honor the conditional headers on the way back in. When a request arrives with If-None-Match/If-Modified-Since, compare against the current validator and return 304 (no body) when it still matches, or a fresh 200 when it doesn’t. Again, static-file servers often handle this for you; app routes and edge workers frequently don’t unless you wire it up.
  3. Decide what “changed” means and keep the ETag stable for genuinely-unchanged content — don’t let recompression or per-server variance flip it needlessly.
  4. Watch for the classic misconfigurations:
    • Always-200 — never emitting validators, so no request is ever conditional and you never get the efficiency benefit.
    • Unstable ETags — a value that changes even when content doesn’t (load balancers, recompression), forcing constant re-fetches.
    • Stale 304s — the dangerous one: a server that keeps returning 304 (or an unchanged ETag) after content has genuinely changed, so crawlers and caches never pick up the update. That’s a bug to catch in log analysis, not an inherent flaw of 304.

304 vs. other status codes

304 vs. 301 / 302 / 307 / 308

Those are the actual redirects. A 301/308 (permanent) or 302/307 (temporary) carries a Location header and moves the client to a different URL — and 301/308 also pass a canonicalizationHow search engines pick one canonical URL among duplicates and consolidate signals onto it. signal. A 304 has no Location, moves nobody, and passes no ranking signal. Same 3xx family by number, completely different job. The per-code redirect details live in their own articles (see the 301 redirectA 301 redirect is the HTTP status code for a permanent move: it tells browsers and search engines a URL has moved for good, and it's the strongest signal for consolidating a page's ranking signals onto the new URL. Google says permanent redirects don't cause a loss in PageRank. piece and the redirects sub-hub).

304 vs. 204 No Content

Both are bodyless, for entirely different reasons. A 204 No Content is a 2xx success with an intentionally empty body because the server has genuinely nothing to send — a successful API DELETE/PUT, an analytics beacon. A 304 also sends no body, but not because there’s nothing to send; it’s because “you already have it and it’s still valid.” Don’t conflate them: a 204 on a page URL can be treated like a soft 404A soft 404 is a URL that returns a success status code (usually 200 OK) even though the page is empty, missing, or shows a 'not found' message. It isn't a status code a server sends — it's a label search engines apply after comparing the response code against the rendered content, and they treat the page like a 404 for indexing. because there’s no indexable content, whereas a 304 is a validity confirmation for content Google already has. The 204 No ContentHTTP 204 No Content is a 2xx success status meaning the server fulfilled the request but is intentionally sending back no message body. It's correct for APIs and beacons — but on a page meant to be indexed, Google treats it like a soft 404. article covers that code in full.

Common myths about 304

  1. “304 is a redirect.” No Location header, nobody goes anywhere. The client reuses its own cached copy. RFC 9110’s “redirecting the client to make use of that stored representation” means pointing back to the cache, not to another URL.
  2. “304 is an error I need to fix.” It’s the correct, intended outcome of a working conditional-request setup. Seeing 304s in a crawl or DevTools is a sign caching is working — the single most common misconception in the SERP.
  3. “304 helps rankings.” No direct ranking effect exists, and Google bounds the indexing effect too — Search may recalculate a URL’s signals, but a 304 otherwise doesn’t change indexing. The benefit is resource savings that Google says may indirectly improve crawl efficiency on very large sites, but it’s not a ranking signal and not a guarantee that saved effort moves to other URLs.
  4. “If my server returns 304, Google will use stale content forever.” 304 only fires while the validator still matches. The moment content actually changes, a correctly implemented server returns a fresh 200 with new validators. The real risk is a misconfigured server that keeps returning 304 after content changed — a bug, not a property of 304.
  5. “ETag and Last-Modified are interchangeable.” Both are validators, but Last-Modified is date-format-sensitive and only as granular as its timestamp, while ETag is opaque and precise (and can vary awkwardly across servers or after recompression if implemented carelessly). Google recommends ETag as primary, both if you can.

FAQs

Is HTTP 304 an error? No — it’s a success signal that caching is working. It means the client’s cached copy is still valid.

Is 304 Not ModifiedHTTP 304 Not Modified is the response to a conditional GET/HEAD whose condition evaluates false, in the 3xx class but not a redirect: it has no Location header and no body. It tells a client its cached copy is still valid. For SEO it has no direct ranking effect and no indexing effect beyond a possible signal recalculation, though it can help crawl efficiency indirectly on large sites. a redirect? No. It’s in the 3xx class by numbering, but it has no Location header and doesn’t move the client to a new URL.

Does 304 help SEO or rankings? No direct ranking effect, and no indexing effect beyond Google possibly recalculating a URL’s signals. It saves bandwidth and compute by letting crawlers skip unchanged pages, which Google says may indirectly improve crawl efficiency on large sites.

What’s the difference between ETag and Last-Modified? ETag is an opaque version fingerprint matched via If-None-Match; Last-Modified is a timestamp matched via If-Modified-Since. Google recommends ETag as less error-prone.

What is a weak vs. a strong ETag? A strong ETag asserts byte-identical content; a weak ETag (prefixed W/) asserts semantic equivalence, tolerating trivial differences like compressionCompression (HTTP content encoding) shrinks text-based responses — HTML, CSS, JS, JSON, SVG, XML sitemaps — before they're sent over the network, using an algorithm like Gzip, Brotli, or Zstd, so the browser or crawler downloads fewer bytes. It's not a ranking factor, but it speeds up page loads and helps pages stay under crawler fetch limits. or a changed footer date.

Why do I see 304s in my logs or crawl reports? Because clients are making conditional requests and your server is correctly confirming content hasn’t changed. That’s expected and good.

How do I make my server return 304 correctly? Send ETag/Last-Modified on the 200, then honor If-None-Match/If-Modified-Since on the next request by returning 304 with no body when the validator still matches.

What’s the difference between 304 and 204? Both are bodyless; 204 because there’s nothing to send, 304 because you already have a still-valid copy.

Does Googlebot send conditional headers on every request? No — caching support varies by crawler, so not every request will be conditional even with headers configured.

Can a 304 response have a body? No. Per RFC 9110, “it cannot contain content or trailers.” A 304 with a body is a spec violation.

TIP Inspect the validators before calling a 304 an error

Conditional caching depends on stable ETag or Last-Modified values and a bodyless 304 response. One ordinary header fetch does not reproduce the complete handshake.

Use my HTTP Header Checker to inspect validators and cache policy, then send a conditional request to verify the server returns 304 only while content is unchanged. HTTP Header Checker Free

  1. Inspect ETag, Last-Modified, Cache-Control, and Vary on the initial 200 response.
  2. Repeat the request with If-None-Match or If-Modified-Since and require a bodyless 304 when unchanged.
  3. Change substantive content and verify the server returns a fresh 200 with an updated validator.

Try it live

This is a real endpoint on this site — not a simulation. Hit it from the button, open it in a new tab, or curl -i it from your terminal, and the server answers with the actual status code this article is about.

Open in new tab ↗

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.