Fix Cache-Control Headers on Nginx
Last updated: April 2026
Without Cache-Control headers, browsers and CDNs apply unpredictable default caching. Set explicit values for each resource type.
Audit your Nginx cache headers live โStatic assets โ cache forever with fingerprinting
location ~* \.(js|css|woff2|woff|ttf)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
Images
location ~* \.(png|jpg|jpeg|gif|ico|svg|webp)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000" always;
}
HTML pages โ always revalidate
location ~* \.html$ {
add_header Cache-Control "no-cache" always;
}
# Or for the root
location = / {
add_header Cache-Control "no-cache" always;
try_files $uri $uri/ /index.html;
}
API responses โ no caching
location /api/ {
add_header Cache-Control "no-store" always;
proxy_pass http://backend;
}
stale-while-revalidate for proxy cache
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m max_size=100m;
location /api/public/ {
proxy_cache api_cache;
proxy_cache_valid 200 60s;
proxy_cache_use_stale updating error timeout;
proxy_cache_background_update on;
add_header Cache-Control "public, max-age=60, stale-while-revalidate=300" always;
proxy_pass http://backend;
}
Complete server block example
server {
listen 443 ssl;
server_name yoursite.com;
# Static assets โ long cache
location ~* \.(js|css|woff2)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
# Images โ medium cache
location ~* \.(png|jpg|jpeg|webp|svg)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000" always;
}
# HTML โ always revalidate
location ~* \.html$ {
add_header Cache-Control "no-cache" always;
}
# API โ no cache
location /api/ {
add_header Cache-Control "no-store" always;
proxy_pass http://backend;
}
}
Verify and reload
nginx -t && nginx -s reload curl -sI https://yoursite.com/app.js | grep -i cache-control curl -sI https://yoursite.com/api/data | grep -i cache-control