How to Set Cache-Control & Long-Lived Expires for Static Assets in Nginx
Modern bundlers (Webpack, Vite, Turbopack) include content hashes in asset filenames (e.g. app.8f2a91.js). Setting aggressive caching policies ensures visitors only download assets once.
Interactive Nginx Config Generator
server {
listen 80;
server_name example.com;
root /var/www/site;
# 1 Year Cache for hashed immutable production assets
location ~* \.(?:css|js|woff2?|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
# Media assets (images, audio, video) cached for 30 days
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|webp|avif)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
access_log off;
}
# Disable caching for dynamic HTML files and service workers
location ~* \.(?:html?|xml|json)$ {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
}
}Directives & Architecture Explained
expires 1y;Sets both Expires HTTP header and max-age directive to 31536000 seconds (1 year).
immutable;Tells modern browsers that the file content will never change during its lifetime, preventing 304 conditional revalidation queries on page reload.
access_log off;Disables access logging for static asset requests to eliminate disk I/O bottlenecks.
Production Verification & Reload Workflow
sudo nginx -tValidate regex location blocks
curl -I http://example.com/assets/main.cssCheck Cache-Control and Expires response headers
Production Troubleshooting Tips
- β’Never mark non-hashed assets (like favicon.ico or logo.png) as immutable unless you never plan to update them.
- β’If you update a CSS file without cache-busting query strings or content hashes, users will not see changes until their cache expires.
- β’Check browser DevTools Network tab to confirm assets load with status "(from disk cache)" or "(from memory cache)".
Frequently Asked Questions
What does immutable mean in Cache-Control?
It tells browsers not to send If-None-Match or If-Modified-Since requests when the user presses refresh (F5), saving network roundtrips completely.
Why turn access_log off for images and CSS?
On high-traffic sites, logging every single font, icon, and CSS file can thrash server disk IOPS without providing meaningful analytics.