How to Cache Static Assets with Nginx for Faster Websites
Why Static Asset Caching Matters
Every time a user visits your website, their browser requests dozens of static assets: CSS files, JavaScript, images, fonts, and more. Without proper caching, each request hits your server, increasing load times and bandwidth usage. Nginx, a high-performance web server, can dramatically improve this by telling browsers to cache these assets locally and by caching them on the server side.
In this guide, you'll learn how to configure Nginx to cache static assets effectively, reducing latency and server load.
Understanding Browser Caching vs. Server-Side Caching
There are two main types of caching relevant here:
- Browser caching: The browser stores assets locally based on HTTP headers like
Cache-ControlandExpires. Subsequent visits load assets from disk, avoiding network requests. - Server-side caching: Nginx stores responses in memory or on disk and serves them directly for repeated requests, reducing backend load.
Both are crucial for performance. We'll cover both.
Step 1: Configure Browser Caching with Expires Headers
The simplest way to enable browser caching is by adding expires directives to your Nginx configuration. This sets the Expires and Cache-Control headers.
Open your site's configuration file (e.g., /etc/nginx/sites-available/example.com) and add a location block for static assets:
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
This tells the browser to cache these files for one year. The immutable directive indicates the file will never change, so the browser won't even revalidate on reload.
Best practice: Use versioned filenames (e.g., style.abc123.css) so you can update assets without breaking caching. When the file changes, the filename changes, and the browser fetches the new version.
Step 2: Enable Server-Side Caching with Proxy Cache
If you're running an application server (like Node.js, Python, or PHP) behind Nginx, you can cache static responses in Nginx to reduce backend requests.
First, define a cache path in the http block of nginx.conf:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=static_cache:10m max_size=1g inactive=60m use_temp_path=off;
Then, in your server block, use it:
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg)$ {
proxy_cache static_cache;
proxy_cache_valid 200 302 1y;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://backend;
}
This caches successful responses for one year and serves stale content if the backend is down. The X-Cache-Status header helps you debug (HIT, MISS, etc.).
Step 3: Optimize with Compression and HTTP/2
Caching reduces requests, but you can further speed up transfers by enabling gzip compression and HTTP/2.
Enable gzip in nginx.conf:
gzip on;
gzip_types text/css application/javascript image/svg+xml;
gzip_min_length 1000;
HTTP/2 multiplexes requests and is enabled by adding http2 to your listen directive:
listen 443 ssl http2;
Note: HTTP/2 requires HTTPS. If you haven't set up TLS yet, see our guide on how HTTPS and TLS work.
Comparison: Cache-Control Directives
| Directive | Meaning | Recommended For |
|---|---|---|
public |
Response can be cached by any cache | Static assets |
private |
Response is for a single user | User-specific data |
no-cache |
Must revalidate with server | HTML pages |
no-store |
Never cache | Sensitive data |
immutable |
Content will not change | Versioned assets |
Step 4: Test and Verify
After applying changes, reload Nginx: sudo nginx -s reload. Then test with curl:
curl -I https://example.com/style.css
Look for Cache-Control and Expires headers. You can also use browser DevTools (Network tab) to see if assets are served from cache (Status 200 or 304).
For server-side cache, monitor the X-Cache-Status header.
Common Pitfalls and Best Practices
- Don't cache HTML: HTML should be dynamic or have short cache times to reflect updates.
- Use versioning: Append hashes to filenames to bust cache when content changes.
- Set appropriate max-age: One year is safe for versioned assets.
- Monitor cache size: Server-side caches can grow; set
max_sizeandinactive. - Consider CDN: For global audiences, a CDN can cache assets closer to users.
FAQ
How do I know if my assets are being cached?
Check response headers with curl -I or browser DevTools. Look for Cache-Control: public, max-age=31536000 and Expires headers. In DevTools, the Size column will show "(from disk cache)" for cached assets.
What's the difference between expires and Cache-Control?
expires sets an absolute date, while Cache-Control uses relative seconds. Cache-Control takes precedence in modern browsers. Nginx's expires directive sets both for compatibility.
Can I cache dynamic content?
Yes, but with caution. Use proxy_cache with short TTLs and consider cache keys based on cookies or headers. For user-specific content, use private or avoid caching.
Speed Up Your Workflow with TryQuickToolBox
While optimizing your site's performance, you might need to compress images or PDFs. Try our Image Compressor to reduce image sizes without losing quality, complementing your Nginx caching strategy.