Nginx Static Asset Caching With Cache Control and ETag Headers
Quick answer
For static assets, the best Nginx setup combines fingerprinted filenames with long-lived Cache-Control headers, so browsers cache files for a long time and...
For static assets, the best Nginx setup combines fingerprinted filenames with long-lived Cache-Control headers, so browsers cache files for a long time and automatically fetch new ones when the name changes. HTML files should be cached briefly or revalidated, since they reference the current asset names. ETag and Last-Modified headers let browsers revalidate cheaply when a cached copy expires.
How Does Browser Caching Work for Static Files?
When a browser downloads a file, it can store it and reuse it without asking the server again, as long as the response says it is still fresh. The Cache-Control header controls this, using directives such as max-age, which sets how many seconds a cached copy stays fresh.
When the copy becomes stale, the browser can revalidate instead of downloading again. It sends the validator it saved, either an ETag or a Last-Modified date, and the server answers with a small 304 Not Modified response if the file has not changed. This saves bandwidth, though it still costs a round trip.
The aim is to avoid even that round trip for files that rarely change, while guaranteeing that users get updates promptly when they do.
What Is the Fingerprinting Strategy?
Caching for a long time is safe only if changing a file also changes its URL. Build tools can add a content hash to filenames, producing names such as app.3f9a2c.js. When the content changes, the hash changes, so browsers treat it as a new resource and fetch it.
With fingerprinted files, you can tell browsers to cache them for a year and mark them immutable, since a given URL will never change content.
location /assets/ {
root /var/www/app;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
location = /index.html {
root /var/www/app;
add_header Cache-Control "no-cache";
}
The no-cache directive does not mean no storage. It means the browser must revalidate before reusing the file, which keeps HTML current while still allowing 304 responses. Check how the immutable directive is supported by the browsers you target.
How Do ETag and Last-Modified Fit In?
Nginx sends both headers for static files by default. Last-Modified is based on the file's modification time, and ETag is derived from the modification time and file size. Both enable conditional requests.
- Keep ETag enabled for files without fingerprints, such as HTML and unversioned images.
- Be careful when serving the same files from several servers. If their modification times differ, validators differ too, causing needless re-downloads. Deploy files with consistent timestamps or adjust the configuration.
- Compressed responses can complicate validators, and the Vary header must indicate encoding differences. Review the Nginx documentation for your version.
- Do not rely on validators for assets that change on every deploy without fingerprints. Users may see stale files until the cache expires.
Nginx also provides the expires directive, which sets both Expires and Cache-Control max-age. Using explicit Cache-Control headers is clearer for modern setups.
What Pitfalls Should You Avoid?
The most common problem is long caching of files without fingerprints. Users keep seeing old JavaScript or CSS after a deploy, and a hard refresh is the only fix. If you cannot fingerprint, use short lifetimes or revalidation for those files.
- Do not cache responses that contain personal data with public caching directives. Use private or no-store where appropriate.
- Remember that CDNs and proxies have their own caching rules, which may require headers such as s-maxage.
- Test headers with a command line client and the browser developer tools, and inspect the cache status of repeat requests.
- Keep old fingerprinted files available for a while after a deploy, so users with an old HTML page can still load the assets it references.
Also enable compression for text assets, and set correct content types, since both affect performance and caching behavior.
Frequently Asked Questions
What is the difference between no-cache and no-store?
No-cache allows storage but requires revalidation before reuse. No-store forbids storing the response at all.
How long should I cache fingerprinted assets?
A year is common, since a changed file gets a new URL. Verify that your build really fingerprints every asset.
Why do users still see old files after deploy?
Probably long-lived caching on filenames that did not change. Fingerprint assets or shorten their cache lifetime.
Do I need ETag if I use Last-Modified?
Not strictly, but having both is harmless and the default. ETag handles cases where timestamps are unreliable.
Summary
Fingerprint assets and cache them for a long time, revalidate HTML, and let ETag and Last-Modified support cheap checks. Test headers in real browsers, and check the Nginx documentation for your version.