When examining the HTTP response headers for static assets like facebook.png
, we noticed something peculiar - the max-age
value was set to 120 seconds instead of the expected 86400 seconds (1 day) we configured in our Nginx settings. Additionally, our custom X-Asset
header was completely missing.
location ~* \.(ico|css|js|gif|jpeg|jpg|png|woff|ttf|otf|svg|woff2|eot)$ {
expires 1d;
access_log off;
add_header Pragma public;
add_header Cache-Control "public, max-age=86400";
add_header X-Asset "yes";
}
After thorough investigation, I discovered that Nginx has specific behavior regarding header inheritance. The issue stems from these key factors:
- Nginx processes directives in a hierarchical manner
- Header directives aren't inherited by default
- The
expires
directive might create its own Cache-Control header - Multiple
add_header
directives in different blocks can override each other
Here's the corrected configuration that ensures proper Cache-Control headers:
server {
# ... other server config ...
# Static assets location block
location ~* \.(ico|css|js|gif|jpeg|jpg|png|woff|ttf|otf|svg|woff2|eot)$ {
expires 1d;
access_log off;
add_header Pragma public;
add_header Cache-Control "public, max-age=86400";
add_header X-Asset "yes";
# Important: prevent inheritance issues
add_header Last-Modified "";
# Ensure no conflicting expires directive
etag on;
try_files $uri =404;
}
# ... remaining server config ...
}
The solution involves several important adjustments:
- We explicitly set
Last-Modified
to empty to prevent default header generation - Added
etag on
to ensure proper cache validation - Included
try_files
to handle missing files properly - Made sure the location block matches all static asset extensions
After applying these changes, restart Nginx and verify with curl:
curl -I https://www.example.com/icons/facebook.png
You should now see the correct headers:
HTTP/1.1 200 OK
Server: nginx/1.11.2
Date: Wed, 05 Oct 2016 09:15:22 GMT
Content-Type: image/png
Content-Length: 416
Last-Modified: Mon, 03 Oct 2016 18:18:37 GMT
Connection: keep-alive
ETag: "57f2a0fd-1a0"
Expires: Thu, 06 Oct 2016 09:15:22 GMT
Cache-Control: public, max-age=86400
Pragma: public
X-Asset: yes
For more complex setups, consider these additional optimizations:
# Map file extensions to cache durations
map $request_uri $cache_control {
default "public, max-age=600";
"~*\.(ico|css|js)$" "public, max-age=31536000, immutable";
"~*\.(gif|jpeg|jpg|png|svg)$" "public, max-age=86400";
"~*\.(woff|ttf|otf|woff2|eot)$" "public, max-age=604800";
}
server {
# ... server config ...
location ~* \.(ico|css|js|gif|jpeg|jpg|png|woff|ttf|otf|svg|woff2|eot)$ {
expires max;
add_header Cache-Control $cache_control;
add_header X-Asset "yes";
access_log off;
}
}
When examining the HTTP response headers for static assets on my Nginx server, I noticed the Cache-Control settings weren't being applied as expected. Despite configuring:
location ~* \.(ico|css|js|gif|jpeg|jpg|png|woff|ttf|otf|svg|woff2|eot)$ {
expires 1d;
access_log off;
add_header Pragma public;
add_header Cache-Control "public, max-age=86400";
add_header X-Asset "yes";
}
The actual response showed a max-age of 120 seconds instead of the intended 86400 (1 day):
Cache-Control:public, max-age=120
The root cause appears to be Nginx's expires
directive interfering with manual Cache-Control headers. When both are present, Nginx may generate its own Cache-Control header, overriding custom settings.
Here's the corrected configuration that ensures proper cache control:
location ~* \.(ico|css|js|gif|jpeg|jpg|png|woff|ttf|otf|svg|woff2|eot)$ {
# Disable default expires behavior
expires off;
# Custom cache headers
add_header Cache-Control "public, max-age=86400, immutable";
add_header Pragma public;
add_header X-Asset "yes";
# Performance optimizations
access_log off;
log_not_found off;
# Enable sendfile for better performance
sendfile on;
tcp_nopush on;
}
1. Disable expires directive: This prevents Nginx from generating conflicting Cache-Control headers
2. Explicit Cache-Control: We manually set all required caching parameters
3. Added immutable: For static assets that never change, this prevents unnecessary revalidation
After making these changes and reloading Nginx (nginx -s reload
), verify with curl:
curl -I https://www.example.com/icons/facebook.png
Should now show:
HTTP/1.1 200 OK
Server: nginx/1.11.2
Date: Tue, 04 Oct 2016 15:30:22 GMT
Content-Type: image/png
Content-Length: 416
Last-Modified: Mon, 03 Oct 2016 18:18:37 GMT
Connection: keep-alive
Cache-Control: public, max-age=86400, immutable
ETag: "57f2a0fd-1a0"
X-Asset: yes
For more granular control, consider these variations:
# Different cache durations per file type
location ~* \.(js|css)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
location ~* \.(jpg|jpeg|png|gif|ico)$ {
add_header Cache-Control "public, max-age=2592000";
}
# Versioned assets with infinite cache
location ~* \.(js|css|woff2?|ttf|otf|eot|svg)[a-zA-Z0-9=_\-]+\.(js|css|woff2?|ttf|otf|eot|svg)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
- Not clearing browser cache when testing changes
- Forgetting to reload Nginx after configuration changes
- Setting too long cache durations for frequently updated assets
- Overriding headers in other configuration blocks