# Nginx Cache Not Working
Nginx's proxy cache should store responses and serve them quickly, but cache misses happen on every request. Cached content doesn't update, or the wrong content gets cached. Pages that should be fast remain slow because the cache isn't doing its job.
Understanding Nginx Cache Mechanics
- 1.Nginx proxy cache works by:
- 2.Computing a cache key from request attributes
- 3.Checking if a cached response exists
- 4.Serving cached content if valid
- 5.Otherwise, fetching from upstream and storing
Cache problems occur when: - Cache key doesn't match - Cache zone not configured - Upstream headers prevent caching - Cache bypass conditions trigger
Check if cache is being used:
``bash
# Add cache status to response header
curl -I http://example.com/api/data | grep X-Cache-Status
Possible values: HIT, MISS, BYPASS, EXPIRED, UPDATING, REVALIDATED, STALE.
Common Cause 1: Cache Zone Not Configured
The cache zone must be defined before it can be used.
Problematic config:
``nginx
location /api {
proxy_pass http://backend;
proxy_cache mycache; # mycache not defined
}
Solution: Define cache zone in http block: ```nginx http { # Define cache zone proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mycache:10m max_size=100m inactive=60m use_temp_path=off;
server { location /api { proxy_pass http://backend; proxy_cache mycache; proxy_cache_valid 200 10m; add_header X-Cache-Status $upstream_cache_status; } } } ```
Verify cache directory exists:
``bash
ls -la /var/cache/nginx/
# If missing, create it
sudo mkdir -p /var/cache/nginx
sudo chown www-data:www-data /var/cache/nginx
Common Cause 2: Cache Key Mismatch
Every request generates a different cache key, preventing reuse.
Default cache key: $scheme$proxy_host$request_uri
Problem: Query parameters change:
``
/api/data?page=1
/api/data?page=1&sort=name
/api/data?page=1&_=12345 # Cache buster
Each is a different key, no cache hit.
Solution: Normalize cache key: ```nginx location /api { proxy_pass http://backend; proxy_cache mycache;
# Ignore certain query parameters proxy_cache_key "$scheme$host$request_uri";
# Or be more specific proxy_cache_key "$scheme$host$uri$is_args$args";
# Or ignore all query params proxy_cache_key "$scheme$host$uri"; } ```
Better: Ignore cache busters: ```nginx # Remove _ timestamp parameter before caching map $args $cleaned_args { ~(^|.*&)_(=[^&]*)?(.*)$ $3; default $args; }
location /api { proxy_cache_key "$scheme$host$uri?$cleaned_args"; } ```
Common Cause 3: Upstream Headers Prevent Caching
Backend sends headers that prevent caching.
Check backend headers:
``bash
curl -I http://backend:3000/api/data
Look for:
- Cache-Control: no-cache, no-store, private
- Expires in the past
- Set-Cookie (Nginx doesn't cache responses with cookies by default)
- Vary with too many headers
Solution: Override caching headers: ```nginx location /api { proxy_pass http://backend; proxy_cache mycache; proxy_cache_valid 200 10m;
# Ignore upstream cache headers proxy_ignore_headers Cache-Control Expires Set-Cookie;
# Force caching proxy_cache_valid any 10m;
add_header X-Cache-Status $upstream_cache_status; } ```
But be careful with cookies - you might cache personalized content:
``nginx
# Only cache if no cookie
proxy_cache_bypass $http_cookie;
Common Cause 4: Wrong Cache Validity Settings
proxy_cache_valid determines what gets cached and for how long.
Problematic config:
``nginx
proxy_cache_valid 200 10m;
# Only caches 200 responses, not 301, 302, 404, etc.
Solution: Define validity for each status:
``nginx
proxy_cache_valid 200 302 10m;
proxy_cache_valid 301 1h;
proxy_cache_valid 404 1m;
proxy_cache_valid any 1m; # Cache everything else briefly
Or rely on upstream headers:
``nginx
proxy_cache_valid 200 10m;
# Use Cache-Control max-age from upstream
proxy_cache_max_range_offset 0; # Honor upstream max-age
Common Cause 5: Cache Bypass Conditions
Certain conditions skip the cache entirely.
Problematic config:
``nginx
location /api {
proxy_cache mycache;
proxy_cache_bypass $http_pragma; # Bypass on Pragma header
proxy_cache_bypass $http_authorization; # Bypass if Authorization header
}
Common bypass triggers: - Authorization header present - Pragma: no-cache - Cache-Control: no-cache from client - Admin/logged-in user cookies
Solution: Control bypass conditions: ```nginx location /api { proxy_cache mycache;
# Only bypass for specific conditions proxy_cache_bypass $cookie_nocache $arg_nocache;
# Don't bypass for Authorization - if content is same for all users proxy_cache_bypass off;
add_header X-Cache-Status $upstream_cache_status; } ```
Common Cause 6: Cache Lock Issues
Concurrent requests for same uncached content all hit backend.
Problem:
``
Request 1: MISS -> fetches from backend -> caches
Request 2: MISS (simultaneous) -> also fetches from backend
Request 3: MISS (simultaneous) -> also fetches from backend
Solution: Enable cache lock: ```nginx location /api { proxy_cache mycache; proxy_cache_valid 200 10m;
proxy_cache_lock on; proxy_cache_lock_timeout 5s;
add_header X-Cache-Status $upstream_cache_status; } ```
Now only first request fetches; others wait for cache.
Common Cause 7: Vary Header Expansion
Vary header causes multiple cache entries for same URL.
Backend sends:
``
Vary: Accept-Encoding, Accept-Language
Nginx creates separate cache entries for each combination:
``
Accept-Encoding: gzip, Accept-Language: en
Accept-Encoding: gzip, Accept-Language: fr
Accept-Encoding: identity, Accept-Language: en
Solution: Limit Vary or normalize headers: ```nginx location /api { proxy_cache mycache;
# Ignore Vary header from upstream proxy_ignore_headers Vary;
# Or handle Vary yourself proxy_cache_key "$scheme$host$uri$http_accept_encoding"; } ```
Common Cause 8: Stale Content Not Updating
Cache serves outdated content after backend changes.
Problem:
``nginx
proxy_cache_valid 200 10m;
# Content cached for 10 minutes, backend updated after 2 minutes
# Users see old content for 8 more minutes
Solution 1: Background update: ```nginx location /api { proxy_cache mycache; proxy_cache_valid 200 10m;
# Serve stale content while updating in background proxy_cache_use_stale updating; proxy_cache_background_update on;
add_header X-Cache-Status $upstream_cache_status; } ```
Solution 2: Proactive purge: ```nginx location /api { proxy_cache mycache;
# Allow manual cache purge proxy_cache_purge $http_purge_method; }
# Purge cache with: curl -X PURGE http://example.com/api/data ```
Solution 3: Shorter validity:
``nginx
proxy_cache_valid 200 1m;
Common Cause 9: Cache Not Persisting
Cache directory permissions or disk space issues.
Diagnosis: ```bash # Check cache directory permissions ls -la /var/cache/nginx/
# Check disk space df -h /var/cache
# Check cache files ls -la /var/cache/nginx/
# Count cached files find /var/cache/nginx -type f | wc -l ```
Solution: ```bash # Fix permissions sudo chown -R www-data:www-data /var/cache/nginx
# Create cache directory if missing sudo mkdir -p /var/cache/nginx sudo chown www-data:www-data /var/cache/nginx ```
Verification Steps
- 1.Add cache status header:
- 2.```nginx
- 3.add_header X-Cache-Status $upstream_cache_status;
- 4.
` - 5.Make multiple requests:
- 6.```bash
- 7.# First request
- 8.curl -I http://example.com/api/data | grep X-Cache-Status
- 9.# MISS
# Second request curl -I http://example.com/api/data | grep X-Cache-Status # HIT ```
- 1.Check cache files:
- 2.```bash
- 3.ls -la /var/cache/nginx/
- 4.
` - 5.Monitor cache logs:
- 6.```nginx
- 7.log_format cache '$remote_addr - $upstream_cache_status [$time_local] '
- 8.'$request $status $body_bytes_sent';
access_log /var/log/nginx/cache.log cache; ```
- 1.Check cache statistics:
- 2.```nginx
- 3.# Add to server block
- 4.location /cache_status {
- 5.set $cache_hits $sent_http_x_cache_hits;
- 6.return 200 "Cache status: $upstream_cache_status\n";
- 7.}
- 8.
`
Complete Working Configuration
```nginx http { proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:50m max_size=500m inactive=60m use_temp_path=off;
# Ignore cache-busting query params map $args $clean_args { ~(^|.*&)_(=[^&]*)?(.*)$ $3; default $args; }
server { location /api { proxy_pass http://backend;
proxy_cache api_cache; proxy_cache_key "$scheme$host$uri?$clean_args"; proxy_cache_valid 200 10m; proxy_cache_valid 404 1m; proxy_cache_valid any 5m;
proxy_cache_lock on; proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504; proxy_cache_background_update on;
proxy_ignore_headers Set-Cookie; add_header X-Cache-Status $upstream_cache_status;
add_header Set-Cookie ""; # Clear any cookies } } } ```
Quick Reference
| X-Cache-Status | Meaning | What to Check |
|---|---|---|
| MISS | Not in cache | Cache zone exists? Cache key consistent? |
| BYPASS | Cache skipped | Cache bypass conditions? Authorization headers? |
| EXPIRED | Was cached, now expired | Increase validity time or background update |
| UPDATING | Stale content while updating | proxy_cache_use_stale configured |
| STALE | Serving old content due to backend error | Intentional fallback behavior |
| HIT | Found in cache | Working correctly |
Cache issues usually come from configuration gaps: missing zones, inconsistent keys, or upstream headers that prevent caching. Check the X-Cache-Status header to understand what's happening.