I'm trying to have a basic behavior regarding my caching strategy: files should be cached, and revalidated with server each time. So I would like Apache to send a 304 back.

Here is the dialog that happens for each browser refresh:

Status Code:200 OK

Request Headers

Cookie: ...
If-Modified-Since:Tue, 14 Oct 2014 15:10:37 GMT
User-Agent:Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36

Response Headers

Date:Tue, 14 Oct 2014 16:58:05 GMT
Keep-Alive:timeout=5, max=99
Last-Modified:Tue, 14 Oct 2014 15:10:37 GMT
Server:Apache/2.4.6 (Ubuntu)

(this is from chrome devtools, with Disable cache unchecked)

You can see that the response contains the Cache-Control:No-cache Header, and that the If-modified-since header matches the Last-modified. The ETag matches too.

Shouldn't Apache sends a 304 in that case ?


Disabling ETags in apache with

 Header  unset ETag

makes the caching behavior more predictible...

  • I think Cache-Control:max-age=0 disabled the cache, so you see the Cache-Control:No-cache response.
    – ThoriumBR
    Commented Oct 14, 2014 at 17:24
  • I explicitly set Cache-Control:No-cache in my apache config because from w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.1, I understand that it causes revalidation for each request. Does revalidation implies re-sending the file ? I would say it should use If-modified-since to determine if it's a 200 or a 304.
    – zrz
    Commented Oct 14, 2014 at 17:37

4 Answers 4


This seems to be an old bug, explaining why Header unset ETag makes a difference.

Apache 2.4.0+ automatically appends the compression method name to the ETag (as seen in your headers), and prevents a 304 response.

Newest versions of mod_deflate support a DeflateAlterETag that can be used to control this behavior:

DeflateAlterETag NoChange
  • 3
    This is correct but Apache 2.4 doesn't contain that option, only Apache 2.5. However personally I don't find ETags that useful since Apache bases them off the last modified date, rather than the file contents. So turning off ETags falls back to If-Modified-Since header, which is based on last modified date anyway. You can alter the ETag in Apache to make it based off of size, last-modified and/or inode - with size and last modified being the default - but, until they add an option to calculate an ETag based on checksum of file contents, its of limited use IMHO. So I turn them off. Commented Jun 8, 2015 at 22:04
  • 1
    @BazzaDP That makes sense. 2.5 also has a DeflateAlterETag Remove option to does just that Commented Jun 9, 2015 at 10:17
  • So the most popular webserver on the planet does not properly support client-side caching with compression, and this bug has been at large for more than 10 years. Unbelievable. Commented Jun 25, 2020 at 12:28

This one stands out in the request as being a bit odd:


Likely more important though, I notice that the returned content is html. Is it being dynamically generated? Apache MAY send a 304 response, but unless you're serving static content, it's not Apache's job to make that call, and it comes down to your application logic. E.g. most php applications have limited support for such things.

A front end cache may help, as the caching app can check the modification time, etag, etc., but only if both the application and the request headers are cache friendly. Eg the application has to set appropriate headers to indicate the content is cacheable, and things like the Cache-control header in your request will negate the cache. Your headers are not looking cache friendly.

  • The requested file is a static html file, for which Apache get the modification time right. (Last-Modified:Tue, 14 Oct 2014 15:10:37 GMT). the max-age=0 header is in the request sent by chrome when I type the URL and hit Enter. Is it there because of previous responses ?
    – zrz
    Commented Oct 14, 2014 at 17:43
  • I read that Chrome automatically adds Cache-Control:max-age=0 to request (except for the first time you load Chrome,type the URL, hit enter). But that doesn't seem to affect other servers (CDNs send 304 even with max-age=0 in the request).
    – zrz
    Commented Oct 14, 2014 at 18:37
  • @zrz: limiting intermediary caching is very useful when debugging, but otherwise would harm performance. Check the context of what you read on what chrome does. In terms of what apache does, it's quite configurable. Cache-control is an instruction for intermediary caches, not for the origin server. However, apache can act as an intermediary cache, and can be configured to do all sorts of things. I think if you take out your anti-caching instructions you'll get behaviour more like what you expect of an origin server.
    – mc0e
    Commented Oct 16, 2014 at 15:09

Old bug solved in recent apache versions.

If DeflateAlterETag is not available to you, just install brotli compression and use BrotliAlterEtag NoChange


If you have Apache configured with Cache-Control:No-cache, Apache will never send a HTTP 304 Not modified to the client.

If you want to revalidate some requests, put a Cache-Control:No-cache only on the pages where you need that. You don't need to revalidate all the resources, and you are wasting bandwidth by doing so.

  • I seem to be confused by the term "revalidate". To me, it means to check if it's a 304. Am I wrong ?
    – zrz
    Commented Oct 14, 2014 at 17:48
  • You are wrong. Revalidate is to send the data all again to the client and force him read everything again even if it already have the same information. You are basically disabling the cache on every client.
    – ThoriumBR
    Commented Oct 14, 2014 at 17:50
  • That explains a lot. Last thing I need explained having that in mind is that Apache sends 304 for some resources (png for example), while I still have that Cache-Control set no No-cache in the response and to max-age=0 in the request. Any clue ?
    – zrz
    Commented Oct 14, 2014 at 17:59
  • 1
    @ThoriumBR If I've interpreted you correctly, both of your answers are incorrect here; no-cache (as opposed to no-store) does not mean "don't cache", and may result in a 304 if content hasn't changed. Indeed the OP expected this but wasn't getting it due to the etag issue. must-revalidate relates to how stale content is handled and does not always send "the data all again".
    – Nick
    Commented Feb 26, 2017 at 17:22
  • This is not correct, ThoriumBR. The directive "No-cache" does not disables the client's cache. It just tells the client to at least re-validate the resource every time. If the resource didn't changed, the server will reply with a 304 code without a payload. If you see a 200 code, it comes from the clients cache. Revalidation does NOT forces the server to send the resource contents to the client. It will always use the "If-Last-Modified" line in the request (or the etag) to decide what to do.
    – StanE
    Commented Apr 1 at 17:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .