How to configure ETag with browser caching

22,099

Solution 1

You would use either FileETag MTime Size or Header unset Etag and FileEtag none. Do not use both (Create ETag and Remove ETag) and only choose which one works best on your particular server.

# Create the ETag (entity tag) response header field
FileETag MTime Size

or

# Remove the ETag (entity tag) response header field
Header unset ETag
FileETag none

Solution 2

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:

  1. The server adds ETag header to a response containing a resource being served.

  2. The client caches the resource and remembers its entity tag (the value of ETag).

  3. 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.

  4. 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.

Solution 3

ETAG is not the most important attribute. The main attribute you're missing is expires. I'm 100% percent sure, browser cache will work without etag. Check the below configuration on http://pisrs.si. How to check? Hit F12 in browser, goto network tab, see how resources are fetched, compare to your site. Localhost resources are cached in different way. Check your browser info on that.

Below is working configuration from main domain, that is working. Make sure you have the necessary mods enabled.

<IfModule mod_mime.c>
    AddType text/css .css
    AddType application/x-javascript .js
    AddType image/bmp .bmp
    AddType image/gif .gif
    AddType application/x-gzip .gz .gzip
    AddType image/x-icon .ico
    AddType image/jpeg .jpg .jpeg .jpe
    AddType image/png .png
    AddType application/x-font-ttf .ttf .ttc
    AddType application/zip .zip
</IfModule>
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType text/css A31536000
    ExpiresByType application/x-javascript A31536000
    ExpiresByType application/javascript A31536000
    ExpiresByType text/javascript A31536000
    ExpiresByType text/x-js A31536000
    ExpiresByType image/bmp A31536000
    ExpiresByType image/gif A31536000
    ExpiresByType application/x-gzip A31536000
    ExpiresByType image/x-icon A31536000
    ExpiresByType image/jpeg A31536000
    ExpiresByType application/x-font-otf A31536000
    ExpiresByType image/png A31536000
    ExpiresByType application/x-font-ttf A31536000
    ExpiresByType application/zip A31536000
</IfModule>
<IfModule mod_deflate.c>
    <IfModule mod_headers.c>
        Header append Vary User-Agent env=!dont-vary
    </IfModule>
        AddOutputFilterByType DEFLATE text/html text/css text/x-component application/x-javascript application/javascript text/javascript text/x-js text/plain image/x-icon image/png image/gif
    <IfModule mod_mime.c>
        # DEFLATE by extension
        AddOutputFilter DEFLATE js css htm html xml png gif
    </IfModule>
</IfModule>
<FilesMatch "\.(gif|ico|jpg|jpeg|png|GIF|ICO|JPG|JPEG|PNG|css|js|woff|CSS|JS|WOFF|ttf|TTF)$">
    <IfModule mod_headers.c>
         Header unset Set-Cookie
         Header set Cache-Control "max-age=31536000, public"
    </IfModule>
</FilesMatch>

Solution 4

ETags are just a unique opaque tag, you can't compare them except for equality, so if you have 2 ETags for the same resource (e.g. URI), you can't tell which resource is newer. For that you need the Last-Modified header.

Even the Last-Modified header is problematic, as it only has a resolution down to 1s, and on heavily modified sites (like popular blogs) it's quite possible for a cache to have multiple representations of a file with different ETags and the same Last-Modified value. It's a shame they didn't see fit to make ETags monotonically incrementing so they could be compared.

ETag is used in conditional requests with the If-None-Match header for GETs and If-Match for PUT, and in the first instance, the server should return the full body only if none of the ETag(s) supplied match the current ETag for the resource (it changed), and in the second case (for PUT or PATCH) only if it does match so you're working on the correct version.

Both ETag and Last-Modified are cache validator headers though. Most of the benefit of caching is concerned with the concept of freshness. Validators allow you to check if the version you have is still valid, but that still requires making a request to the server. All you can save is the payload transfer, and for many things nowadays it's just not worth it.

Freshness on the other hand (Expires header or max-age Cache-Control directive) allows the client to avoid re-validation if the version they have already is still fresh (hasn't expired). That saves connecting to the server to check, and so can save a lot of time in page loads.

Share:
22,099
sam
Author by

sam

Updated on September 04, 2020

Comments

  • sam
    sam over 3 years

    I’ve setup a browser cache for a static site via the .htaccess file by setting:

    # BROWER CACHING - 1 Day for images
    <filesMatch ".(jpg|jpeg|gif|ico)$">
    Header set Cache-Control "max-age=86400, public"
    </filesMatch>
    

    I’m fine with these images having a 1 day cache, but the site changes frequently and thus I don’t want to cache the CSS and JS files.

    I’ve read about ETag, which, as I understand, allows you to cache a file, but also set its creation date, so if it gets updated the next time a client visits the site, it will check if the creation date matches.

    1. Have I understood ETag correctly?
    2. How do I configure it? I’ve looked around, but couldn’t find any info on its configuration.
  • Adrien
    Adrien over 7 years
    I disagree. In our proxy cache it would have been very useful to be able to exclude from consideration representations based on comparison of Etag.