HTTP has several features related to caching and they apply to both user agent (browser) cache and proxy caches (whether transparent, or not; e.g. proxy in the client’s network or a reverse proxy sitting just next to the server). These features come in two groups: expiration (may prevent request entirely) and validation (may prevent transfer of data).
Entity tag (ETag) is just one of these features and belongs to the validation group. The other one in this group is last modification time (Last-Modified). Entity tag allows for cache invalidation due to contents change instead of newer last modification time. Read more on how entity tag works on Wikipedia. In short, the typical usage is:
The server adds ETag header to a response containing a resource being served.
The client caches the resource and remembers its entity tag (the value of ETag).
Next time the client needs the resource, it requests it from the server conditionally. In the request, it includes If-None-Match header containing the entity tag.
If the resource changed (the entity tag in If-None-Match is considered to be stale by the server), the server sends a response containing the current version of the resource (and the new entity tag), otherwise it just responds with 304 Not Modified and does not bother to send the resource again.
For static files (not created dynamically by a CGI script or so, on each request), Apache may be configured to generate ETag via FileETag directive. By default, without you making any changes to the configuration, Apache will generate ETag and its value will be based on the file’s last modification time (mtime) and size in Apache 2.4. In Apache 2.3.14, the default used to include the file’s inode number, too.
If the file is served dynamically, Apache cannot generate the ETag, because it does not know the details of how the resource to be cached is generated. It is up to the script to set ETag appropriately and to handle the If-None-Match. E.g. in mod_perl, the If-None-Match part can be handled using Apache2::Request::meets_conditions, which implements handling of HTTP/1.1 conditional requests in general.
If you want to rely solely on ETag, you have to disable other validation features and the expiration mechanism. Set Cache-Control: max-age=0, must-revalidate and Expires: 0 to force the revalidation of cache entries (i.e. always make a request). You may also remove the Last-Modified header from the responses, but HTTP/1.1 advises against that, in general.
For comparison of Last-Modified and ETag, see these:
Note that Last-Modified is seen as a HTTP/1.0 compatibility feature. ETag may contain the same value and work exactly the same (except using If-None-Match instead of If-Modified-Since).
As a side note, I’d like to add that proposed standard RFC 7232 exists and it is related to details of entity tags and conditional requests. See its appendix A for changes it introduces from HTTP/1.1.