Nginx Cache Headers
Introduction
HTTP cache headers are a crucial component of web performance optimization. They instruct browsers and proxy servers on how, when, and for how long to cache resources. When properly configured in Nginx, cache headers can dramatically improve your website's loading times, reduce server load, and decrease bandwidth usage.
In this guide, we'll explore how to configure and optimize cache headers in Nginx. You'll learn about the different types of cache headers, their purpose, and how to implement them effectively in your Nginx configuration.
Understanding HTTP Cache Headers
Before diving into Nginx configuration, let's understand the key HTTP cache headers:
Primary Cache Headers
Cache-Control
: The most important modern caching header that defines caching policiesExpires
: Sets a specific date/time when content becomes staleETag
: A unique identifier for a specific version of a resourceLast-Modified
: Indicates when the resource was last changed
How Caching Works
When a browser requests a resource:
- It first checks its local cache
- If the resource exists and is still valid (according to cache headers), it uses the cached version
- Otherwise, it makes a new request to the server
Configuring Cache Headers in Nginx
Let's look at how to configure these headers in Nginx:
Basic Cache-Control Configuration
The add_header
directive is used to set HTTP headers in Nginx. Here's a basic example:
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
add_header X-Content-Type-Options nosniff;
}
This configuration:
- Matches static files with the specified extensions
- Sets them to expire after 30 days
- Adds a
Cache-Control
header withpublic
(can be cached by browsers and proxies) andmax-age=2592000
(seconds, equivalent to 30 days)
Common Cache-Control Directives
The Cache-Control
header supports several directives:
max-age=<seconds>
: How long the resource is freshs-maxage=<seconds>
: Similar to max-age but for shared caches (CDNs, proxies)public
: Resource can be cached by browsers and intermediariesprivate
: Resource can only be cached by browsersno-cache
: Must revalidate with server before using cached versionno-store
: Don't cache at all (for sensitive data)
Conditional Caching with ETag and Last-Modified
ETags and Last-Modified headers enable conditional requests, which can save bandwidth:
location /api/ {
etag on;
add_header Cache-Control "no-cache";
# Other API configuration...
}
When etag
is enabled:
- Nginx generates an ETag based on the response content
- Browsers send an
If-None-Match
header with the ETag on subsequent requests - If the content hasn't changed, Nginx returns a
304 Not Modified
without sending the full response body
Practical Cache Header Strategies
Let's look at real-world examples for different types of content:
Static Assets (CSS, JS, Images)
location ~* \.(css|js)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
location ~* \.(png|jpg|jpeg|gif|ico|svg|webp)$ {
add_header Cache-Control "public, max-age=31536000";
access_log off;
}
The immutable
directive tells browsers that the content will never change, so they won't revalidate even when users refresh the page.
HTML Content
location ~* \.html$ {
add_header Cache-Control "public, max-age=3600";
}
HTML content typically changes more frequently, so a shorter cache time (1 hour in this example) is appropriate.
API Responses
location /api/ {
add_header Cache-Control "private, no-cache";
etag on;
}
API responses often contain dynamic or personalized data, so we use no-cache
to ensure freshness while still enabling conditional requests.
Vary Header for Content Negotiation
When serving different content based on request headers (like Accept-Encoding or User-Agent):
location / {
add_header Vary "Accept-Encoding, User-Agent";
# Other configuration...
}
This tells caches that the response may differ based on these headers, preventing mismatched content delivery.
Testing Cache Headers
To verify your cache headers are working correctly, you can use:
- Browser DevTools (Network tab)
- Command-line tools like curl:
curl -I https://example.com/style.css
Expected output:
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Mon, 14 Mar 2022 12:00:00 GMT
Content-Type: text/css
Content-Length: 5432
Last-Modified: Sun, 13 Mar 2022 10:15:30 GMT
Connection: keep-alive
ETag: "60ad7-5cc-593d765e9d140"
Cache-Control: public, max-age=31536000, immutable
Common Cache Header Issues and Solutions
Problem: Content Updates Not Reflected
If updates to your site aren't appearing for users:
# Add version parameters to resource URLs
location ~* \.(css|js)$ {
expires max;
add_header Cache-Control "public, max-age=31536000, immutable";
}
Then in your HTML:
<link rel="stylesheet" href="/styles.css?v=123456" />
When you update the content, change the version parameter to force a fresh request.
Problem: Too Much or Too Little Caching
Balance caching duration based on content volatility:
# Frequently changing content
location /news/ {
add_header Cache-Control "public, max-age=900"; # 15 minutes
}
# Rarely changing content
location /about/ {
add_header Cache-Control "public, max-age=604800"; # 1 week
}
Problem: Private Information Being Cached
For personalized content:
location /account/ {
add_header Cache-Control "private, no-store";
}
Complete Nginx Caching Configuration Example
Here's a comprehensive example that covers different content types:
server {
listen 80;
server_name example.com;
root /var/www/html;
# Default caching policy
add_header Cache-Control "no-cache" always;
# Static assets
location ~* \.(css|js)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable" always;
access_log off;
}
# Images, icons, fonts
location ~* \.(ico|gif|jpg|jpeg|png|svg|webp|woff|woff2|ttf|otf|eot)$ {
expires 1M;
add_header Cache-Control "public, max-age=2592000" always;
access_log off;
}
# HTML files
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public, max-age=3600" always;
}
# API endpoints
location /api/ {
add_header Cache-Control "private, no-cache" always;
etag on;
}
# User-specific content
location /user/ {
add_header Cache-Control "private, no-store" always;
}
# Add vary header where appropriate
location / {
add_header Vary "Accept-Encoding" always;
}
}
Summary
Properly configured cache headers in Nginx can significantly improve your website's performance. Key points to remember:
- Use
Cache-Control
as your primary caching directive - Match cache durations to content update frequency
- Use
public
for shared resources andprivate
for user-specific content - Enable
etag
for conditional requests - Use the
Vary
header when serving different content based on request headers
By implementing these practices, you'll reduce server load, decrease bandwidth usage, and provide a faster experience for your users.
Additional Resources
Exercises
- Configure Nginx to cache CSS and JS files for 1 year but HTML files for only 1 hour.
- Set up a conditional cache using ETags for an API endpoint.
- Create a configuration that ensures user profile pages are never cached by shared caches.
- Test your cache headers using curl and browser DevTools.
- Implement a versioning strategy for static assets that allows for long cache times while still enabling immediate updates when needed.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)