TAGS
#Nginx #Caching #ProxyCache #FastCGI #BrowserCache #CacheControl #Expires #Performance #Optimization #CDN #CachePurging #CacheBypass #WebPerformance #StaticContent #DynamicContent #Tutorial #Production #BestPractices #DevOps #Infrastructure
1. Browser Caching with Expires and Cache-Control Headers
Browser caching stores files at the client browser, reducing the number of requests to the server and speeding up load times.
1.1. Expires Header
The Expires header specifies a specific point in time when the cache expires.
Basic syntax:
location ~* \.(jpg|jpeg|png|gif|ico)$ {
expires 30d; # Cache for 30 days
}
Expires values:
server { listen 80; server_name example.com; root /var/www/html;# Images - cache for a long time location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ { expires 1y; # 1 year add_header Cache-Control "public, immutable"; } # CSS and JavaScript - medium cache location ~* \.(css|js)$ { expires 1M; # 1 month add_header Cache-Control "public"; } # HTML - short cache or no-cache location ~* \.html$ { expires 1h; # 1 hour add_header Cache-Control "public, must-revalidate"; } # API responses - no cache location /api/ { expires -1; # No cache add_header Cache-Control "no-cache, no-store, must-revalidate"; }
}
Time units:
expires 1s; # 1 second expires 5m; # 5 minutes expires 2h; # 2 hours expires 7d; # 7 days expires 3M; # 3 months (30 days) expires 1y; # 1 year
expires -1; # No cache (Cache-Control: no-cache) expires epoch; # Expires: Thu, 01 Jan 1970 00:00:01 GMT expires max; # Expires: Thu, 31 Dec 2037 23:55:55 GMT expires off; # Do not set Expires header
Detailed example:
server { listen 80; server_name cdn.example.com; root /var/www/cdn;# Versioned assets - cache forever location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf)$ { # Only apply to files with version in name # e.g., style.v1.2.3.css, app.20240101.js if ($uri ~ "\.v[0-9]+\.|\.v?\d{8,}\.") { expires max; add_header Cache-Control "public, immutable"; } # Files without version expires 1M; add_header Cache-Control "public"; } # Videos - long cache but can revalidate location ~* \.(mp4|webm|ogg|avi)$ { expires 6M; add_header Cache-Control "public, max-age=15552000"; } # Documents - short cache location ~* \.(pdf|doc|docx|xls|xlsx)$ { expires 1d; add_header Cache-Control "public, max-age=86400"; }
}
1.2. Cache-Control Header
Cache-Control is the modern alternative to Expires and is more flexible.
Cache-Control directives:
# public: Can be cached by browser and intermediate caches add_header Cache-Control "public";private: Browser-only cache, not cached by proxies
add_header Cache-Control "private";
no-cache: Must revalidate before using cache
add_header Cache-Control "no-cache";
no-store: Do not cache anything
add_header Cache-Control "no-store";
max-age: Cache duration (seconds)
add_header Cache-Control "public, max-age=31536000";
s-maxage: Max-age for shared caches (CDN, proxies)
add_header Cache-Control "public, max-age=3600, s-maxage=86400";
must-revalidate: Must revalidate when stale
add_header Cache-Control "public, max-age=3600, must-revalidate";
immutable: Content never changes
add_header Cache-Control "public, max-age=31536000, immutable";
no-transform: Do not allow content transformation
add_header Cache-Control "public, no-transform";
Combining directives:
server { listen 80; server_name example.com;# Static assets with versioning - immutable location ~* /static/.*\.(css|js)$ { expires 1y; add_header Cache-Control "public, max-age=31536000, immutable"; } # Images - public, long cache location ~* \.(jpg|png|gif|svg)$ { expires 6M; add_header Cache-Control "public, max-age=15552000"; } # HTML - short cache with revalidation location ~* \.html$ { expires 10m; add_header Cache-Control "public, max-age=600, must-revalidate"; } # User-specific content - private location /dashboard/ { add_header Cache-Control "private, max-age=300"; } # Sensitive data - no cache location /account/ { add_header Cache-Control "private, no-cache, no-store, must-revalidate"; add_header Pragma "no-cache"; add_header Expires "0"; }
}
1.3. Conditional Caching
map $sent_http_content_type $expires { default off; text/html 1h; text/css 1M; application/javascript 1M; ~image/ 1y; application/pdf 7d; ~font/ 1y; }server { listen 80; server_name example.com;
expires $expires; # Override for specific paths location /news/ { expires 5m; add_header Cache-Control "public, max-age=300"; }
}
Cache based on file extension:
server { listen 80; root /var/www/html;# Map file extensions to cache duration location / { if ($request_uri ~* "\.(jpg|jpeg|png|gif|ico)$") { expires 1y; add_header Cache-Control "public, immutable"; } if ($request_uri ~* "\.(css|js)$") { expires 1M; add_header Cache-Control "public"; } if ($request_uri ~* "\.html$") { expires 1h; add_header Cache-Control "public, must-revalidate"; } try_files $uri $uri/ =404; }
}
1.4. ETag and Last-Modified
server { listen 80; server_name example.com;# Enable ETag etag on; # Enable Last-Modified header if_modified_since before; location / { root /var/www/html; # Browser will send If-None-Match (ETag) or If-Modified-Since # Nginx returns 304 Not Modified if file hasn't changed }
}
Example with conditional requests:
server { listen 80; server_name api.example.com;location /api/data { proxy_pass http://backend; # Pass conditional headers to backend proxy_set_header If-Modified-Since $http_if_modified_since; proxy_set_header If-None-Match $http_if_none_match; # Backend returns 304 if not modified # Nginx forwards 304 to client }
}
2. Proxy Caching Basics
Proxy caching stores responses from backend servers, reducing load and response time.
2.1. Proxy Cache Zone Configuration
# Define cache path and settings (in http context) http { proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m use_temp_path=off;server { listen 80; server_name example.com; location / { proxy_pass http://backend; # Enable caching proxy_cache my_cache; # Cache durations proxy_cache_valid 200 302 10m; proxy_cache_valid 404 1m; proxy_cache_valid any 5m; # Add cache status header add_header X-Cache-Status $upstream_cache_status; } }
}
Parameter explanation:
proxy_cache_path /var/cache/nginx/proxy # Path to store cache
levels=1:2 # Directory structure (optimizes I/O)
keys_zone=my_cache:10m # Zone name and shared memory size
max_size=1g # Max cache size on disk
inactive=60m # Remove unused cache after 60 minutes
use_temp_path=off; # Write directly to cache path
Levels explained:
levels=1:2 creates structure: /var/cache/nginx/proxy/ ├── a/ │ ├── 3c/ │ │ └── abc123...def │ └── 7f/ └── b/ └── 2d/
First level: 1 character (a, b, c...) Second level: 2 characters (3c, 7f, 2d...)
2.2. Cache Status Values
$upstream_cache_status can be:
-
MISS : Request not in cache, fetched from backend
-
HIT : Request served from cache
-
EXPIRED : Cache entry expired, fetched from backend
-
STALE : Serving stale content (if configured)
-
UPDATING : Cache is updating from backend
-
REVALIDATED: Cache revalidated with backend (304)
BYPASS : Cache bypassed (per config)
Logging cache status:
http { log_format cache_log '$remote_addr - [$time_local] ' '"$request" $status ' 'Cache: $upstream_cache_status ' 'Time: $upstream_response_time';server { listen 80; access_log /var/log/nginx/cache.log cache_log;
location / { proxy_pass http://backend; proxy_cache my_cache; add_header X-Cache-Status $upstream_cache_status always; }
} }
2.3. Cache Key
The cache key determines the uniqueness of cached items.
Default cache key:
proxy_cache_key $scheme$proxy_host$request_uri;Example:
http://example.com/page?id=1
Key: httpexample.com/page?id=1
Custom cache keys:
location / { proxy_pass http://backend; proxy_cache my_cache;# Include request method proxy_cache_key "$scheme$request_method$host$request_uri"; # Include specific headers # proxy_cache_key "$scheme$host$request_uri$http_accept_language"; # Include cookies # proxy_cache_key "$scheme$host$request_uri$cookie_session";
}
Example with user-specific caching:
map $cookie_user_id $cache_user { default $cookie_user_id; "" "anonymous"; }server { location /api/ { proxy_pass http://backend; proxy_cache user_cache;
# Different cache per user proxy_cache_key "$scheme$host$request_uri$cache_user"; }
}
2.4. Cache Methods and Conditions
server { listen 80;location / { proxy_pass http://backend; proxy_cache my_cache; # Only cache GET and HEAD methods proxy_cache_methods GET HEAD; # Cache durations based on status code proxy_cache_valid 200 301 302 10m; proxy_cache_valid 404 1m; proxy_cache_valid any 5m; # Minimum number of requests before caching proxy_cache_min_uses 3; # Cache even when backend errors proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; # Lock to prevent cache stampede proxy_cache_lock on; proxy_cache_lock_timeout 5s; proxy_cache_lock_age 5s; }
}
proxy_cache_use_stale explained:
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;Serve stale cache when:
- error: Connection error to backend
- timeout: Backend timeout
- updating: Cache is being updated (prevents stampede)
- http_500/502/503/504: Backend returns error codes
2.5. Complete Proxy Cache Example
http { # Define multiple cache zones proxy_cache_path /var/cache/nginx/static levels=1:2 keys_zone=static_cache:10m max_size=500m inactive=60m use_temp_path=off;proxy_cache_path /var/cache/nginx/api levels=1:2 keys_zone=api_cache:10m max_size=200m inactive=10m use_temp_path=off; upstream backend { server backend1.example.com:8080; server backend2.example.com:8080; } server { listen 80; server_name example.com; # Static content cache location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { proxy_pass http://backend; proxy_cache static_cache; proxy_cache_valid 200 30d; proxy_cache_valid 404 1h; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; proxy_cache_lock on; expires 30d; add_header Cache-Control "public, immutable"; add_header X-Cache-Status $upstream_cache_status; } # API cache location /api/ { proxy_pass http://backend; proxy_cache api_cache; proxy_cache_valid 200 5m; proxy_cache_valid 404 1m; proxy_cache_key "$scheme$request_method$host$request_uri$http_authorization"; proxy_cache_methods GET HEAD; proxy_cache_min_uses 2; proxy_cache_use_stale error timeout updating; proxy_cache_lock on; add_header X-Cache-Status $upstream_cache_status; } # No cache for user-specific content location /account/ { proxy_pass http://backend; proxy_cache off; proxy_no_cache 1; proxy_cache_bypass 1; } }
}
3. FastCGI Caching
FastCGI caching is used for dynamic content such as PHP applications.
3.1. FastCGI Cache Configuration
http { # Define FastCGI cache zone fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=php_cache:10m max_size=500m inactive=60m use_temp_path=off;# Cache key fastcgi_cache_key "$scheme$request_method$host$request_uri"; server { listen 80; server_name example.com; root /var/www/html; index index.php index.html; location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # Enable FastCGI cache fastcgi_cache php_cache; # Cache valid durations fastcgi_cache_valid 200 60m; fastcgi_cache_valid 404 10m; # Cache use stale fastcgi_cache_use_stale error timeout updating invalid_header http_500; # Cache locking fastcgi_cache_lock on; fastcgi_cache_lock_timeout 5s; # Minimum uses before caching fastcgi_cache_min_uses 2; # Add cache status header add_header X-FastCGI-Cache $upstream_cache_status; } }
}
3.2. WordPress with FastCGI Cache
http { fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2 keys_zone=wordpress:100m max_size=1g inactive=60m use_temp_path=off;fastcgi_cache_key "$scheme$request_method$host$request_uri"; server { listen 80; server_name blog.example.com; root /var/www/wordpress; index index.php; # Set cache bypass conditions set $skip_cache 0; # POST requests and URLs with query strings if ($request_method = POST) { set $skip_cache 1; } if ($query_string != "") { set $skip_cache 1; } # Don't cache URIs containing the following segments if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") { set $skip_cache 1; } # Don't cache logged in users or recent commenters if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") { set $skip_cache 1; } location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; # FastCGI cache settings fastcgi_cache wordpress; fastcgi_cache_valid 200 60m; fastcgi_cache_valid 404 10m; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; fastcgi_cache_use_stale error timeout updating invalid_header http_500 http_503; fastcgi_cache_lock on; # Headers add_header X-FastCGI-Cache $upstream_cache_status; add_header Cache-Control "public, max-age=3600"; } # Cache static files location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ { expires 30d; add_header Cache-Control "public, immutable"; access_log off; } # Deny access to hidden files location ~ /\. { deny all; access_log off; log_not_found off; } }
}
3.3. Drupal with FastCGI Cache
http { fastcgi_cache_path /var/cache/nginx/drupal levels=1:2 keys_zone=drupal:100m max_size=2g inactive=60m use_temp_path=off;server { listen 80; server_name drupal.example.com; root /var/www/drupal; # Cache bypass conditions set $skip_cache 0; if ($request_method = POST) { set $skip_cache 1; } if ($query_string != "") { set $skip_cache 1; } if ($request_uri ~* "^/(admin|user|cart|checkout)/") { set $skip_cache 1; } if ($http_cookie ~* "SESS") { set $skip_cache 1; } location / { try_files $uri /index.php?$query_string; } location ~ '\.php$|^/update.php' { fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; fastcgi_split_path_info ^(.+?\.php)(|/.*)$; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_cache drupal; fastcgi_cache_valid 200 301 302 5m; fastcgi_cache_valid 404 1m; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; fastcgi_cache_use_stale error timeout updating invalid_header http_500; fastcgi_cache_lock on; add_header X-FastCGI-Cache $upstream_cache_status; } location ~ ^/sites/.*/files/styles/ { try_files $uri @rewrite; expires 30d; add_header Cache-Control "public, immutable"; } }
}
3.4. Custom PHP Application with Cache
http { fastcgi_cache_path /var/cache/nginx/app levels=1:2 keys_zone=app_cache:50m max_size=1g inactive=30m use_temp_path=off;# Custom cache key with user context map $http_cookie $user_context { default "anonymous"; ~*user_id=(?<uid>[^;]+) $uid; } server { listen 80; server_name app.example.com; root /var/www/app/public; # Cache bypass for authenticated users set $skip_cache 0; if ($http_cookie ~* "logged_in") { set $skip_cache 1; } if ($request_uri ~* "/(api|admin)/") { set $skip_cache 1; } location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # Custom cache key fastcgi_cache_key "$scheme$request_method$host$request_uri$user_context"; fastcgi_cache app_cache; fastcgi_cache_valid 200 10m; fastcgi_cache_valid 404 1m; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; fastcgi_cache_use_stale error timeout updating; fastcgi_cache_lock on; fastcgi_cache_min_uses 3; # Custom headers add_header X-Cache-Status $upstream_cache_status; add_header X-Cache-Key "$scheme$request_method$host$request_uri$user_context"; } }
}
4. Cache Keys and Cache Zones
4.1. Cache Zones Configuration
http { # Static content cache - large, long-lived proxy_cache_path /var/cache/nginx/static levels=1:2 keys_zone=static:100m max_size=5g inactive=7d use_temp_path=off;# API cache - smaller, short-lived proxy_cache_path /var/cache/nginx/api levels=1:2 keys_zone=api:50m max_size=1g inactive=1h use_temp_path=off; # User-specific cache proxy_cache_path /var/cache/nginx/user levels=1:2 keys_zone=user:50m max_size=2g inactive=30m use_temp_path=off; # HTML pages cache proxy_cache_path /var/cache/nginx/pages levels=1:2 keys_zone=pages:20m max_size=500m inactive=2h use_temp_path=off;
}
Zone size calculation:
keys_zone size stores cache metadata:
- 1MB ≈ 8000 keys
- 10MB ≈ 80,000 keys
- 100MB ≈ 800,000 keys
Max cache size stores actual content:
- Set based on available disk space
Monitor /var/cache/nginx disk usage
4.2. Advanced Cache Keys
Cache by device type:
map $http_user_agent $device { default desktop; ~mobile mobile; ~tablet tablet; }server { location / { proxy_pass http://backend; proxy_cache my_cache;
# Different cache per device proxy_cache_key "$scheme$host$request_uri$device"; add_header X-Device $device; add_header X-Cache-Status $upstream_cache_status; }
}
Cache by accept headers:
map $http_accept $content_type_key { default "html"; ~application/json "json"; ~application/xml "xml"; }server { location /api/ { proxy_pass http://backend; proxy_cache api_cache;
# Different cache per content type proxy_cache_key "$scheme$host$request_uri$content_type_key"; }
}
Cache by language:
map $http_accept_language $lang { default en; ~^vi vi; ~^ja ja; ~*^zh zh; }server { location / { proxy_pass http://backend; proxy_cache my_cache;
# Different cache per language proxy_cache_key "$scheme$host$request_uri$lang"; add_header Content-Language $lang; }
}
Cache by geo location:
geo $country { default US; 192.168.1.0/24 VN; 10.0.0.0/8 JP; }server { location / { proxy_pass http://backend; proxy_cache my_cache;
# Different cache per country proxy_cache_key "$scheme$host$request_uri$country"; }
}
Complex cache key:
map $http_cookie $user_segment { default "guest"; ~premium=1 "premium"; ~vip=1 "vip"; }server { location /api/ { proxy_pass http://backend; proxy_cache api_cache;
# Cache key combining multiple factors proxy_cache_key "$scheme$host$request_uri$user_segment$device$lang"; add_header X-Cache-Key-Components "segment:$user_segment|device:$device|lang:$lang"; }
}
4.3. Cache Hierarchy
http { # L1 Cache - Memory (small, fast) proxy_cache_path /dev/shm/nginx levels=1 keys_zone=l1_cache:10m max_size=100m inactive=5m use_temp_path=off;# L2 Cache - SSD (medium, fast) proxy_cache_path /var/cache/nginx/l2 levels=1:2 keys_zone=l2_cache:50m max_size=5g inactive=1h use_temp_path=off; # L3 Cache - HDD (large, slower) proxy_cache_path /mnt/cache/nginx/l3 levels=1:2 keys_zone=l3_cache:100m max_size=50g inactive=7d use_temp_path=off; server { location /api/hot/ { # Frequently accessed - L1 cache proxy_pass http://backend; proxy_cache l1_cache; proxy_cache_valid 200 5m; } location /api/warm/ { # Moderately accessed - L2 cache proxy_pass http://backend; proxy_cache l2_cache; proxy_cache_valid 200 1h; } location /api/cold/ { # Rarely accessed - L3 cache proxy_pass http://backend; proxy_cache l3_cache; proxy_cache_valid 200 1d; } }
}
5. Cache Purging and Bypass
5.1. Cache Bypass
server { listen 80;# Bypass cache with special header location / { proxy_pass http://backend; proxy_cache my_cache; # Bypass if X-No-Cache header present proxy_cache_bypass $http_x_no_cache; # Or bypass with cookie proxy_cache_bypass $cookie_nocache; # Or bypass with argument proxy_cache_bypass $arg_nocache; }
}
Multiple bypass conditions:
server { location / { proxy_pass http://backend; proxy_cache my_cache;# Set bypass conditions set $cache_bypass 0; # Bypass for specific cookies if ($http_cookie ~* "admin_logged_in") { set $cache_bypass 1; } # Bypass for specific URLs if ($request_uri ~* "^/(admin|dashboard)/") { set $cache_bypass 1; } # Bypass for POST requests if ($request_method = POST) { set $cache_bypass 1; } # Apply bypass proxy_cache_bypass $cache_bypass; proxy_no_cache $cache_bypass; }
}
Bypass with query parameter:
server { location / { proxy_pass http://backend; proxy_cache my_cache;# ?nocache=1 → bypass cache proxy_cache_bypass $arg_nocache; # ?refresh=1 → refresh cache proxy_cache_bypass $arg_refresh; add_header X-Cache-Bypass $arg_nocache; }}
Usage:
Normal: http://example.com/page
Bypass: http://example.com/page?nocache=1
Refresh: http://example.com/page?refresh=1
5.2. Cache Purging (Nginx Plus / Third-party module)
Nginx Plus purge:
# Nginx Plus only map $request_method $purge_method { PURGE 1; default 0; }server { location / { proxy_pass http://backend; proxy_cache my_cache; proxy_cache_purge $purge_method; } }
Usage:
curl -X PURGE http://example.com/page
Cache purge with open source module:
# Install ngx_cache_purge moduleUbuntu/Debian
sudo apt install libnginx-mod-http-cache-purge
CentOS - compile from source
http { proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=my_cache:10m;server { listen 80; location / { proxy_pass http://backend; proxy_cache my_cache; proxy_cache_key "$scheme$host$request_uri"; } # Purge location location ~ /purge(/.*) { allow 127.0.0.1; allow 10.0.0.0/8; deny all; proxy_cache_purge my_cache "$scheme$host$1"; } }}
Usage:
curl http://example.com/purge/page-to-clear
Manual cache purging:
#!/bin/bashpurge_cache.sh - Manual cache purge script
CACHE_DIR="/var/cache/nginx" CACHE_ZONE="my_cache"
Purge all cache
purge_all() { echo "Purging all cache..." sudo rm -rf ${CACHE_DIR}/${CACHE_ZONE}/* echo "Cache purged!" }
Purge specific URL
purge_url() { local url=$1 local cache_key=$(echo -n "$url" | md5sum | awk '{print $1}') local cache_path=$(find ${CACHE_DIR}/${CACHE_ZONE} -name "${cache_key}")
if [ -n "$cache_path" ]; then echo "Purging cache for: $url" sudo rm -f $cache_path echo "Purged: $cache_path" else echo "No cache found for: $url" fi}
Purge by pattern
purge_pattern() { local pattern=$1 echo "Purging cache matching: $pattern" sudo find ${CACHE_DIR}/${CACHE_ZONE} -type f -name "${pattern}" -delete echo "Done!" }
case "$1" in all) purge_all ;; url) purge_url "$2" ;; pattern) purge_pattern "$2" ;; *) echo "Usage: $0 {all|url|pattern }" exit 1 ;; esac
Cache warmup script:
#!/bin/bashcache_warmup.sh - Warm up cache
URLS=( "http://example.com/" "http://example.com/products" "http://example.com/about" "http://example.com/contact" )
echo "Starting cache warmup..."
for url in "${URLS[@]}"; do echo "Warming up: $url" curl -s -o /dev/null -w "Status: %{http_code}, Time: %{time_total}s\n" "$url" sleep 0.5 done
echo "Cache warmup complete!"
5.3. Selective Cache Invalidation
# API to invalidate cache server { listen 80; server_name example.com;location / { proxy_pass http://backend; proxy_cache my_cache; proxy_cache_valid 200 10m; } # Cache invalidation endpoint location /api/cache/invalidate { allow 127.0.0.1; allow 10.0.0.0/8; deny all; content_by_lua_block { local pattern = ngx.var.arg_pattern if pattern then -- Invalidate cache matching pattern -- Implementation depends on cache module ngx.say("Cache invalidated for pattern: ", pattern) else ngx.say("Pattern required") end } }}
Usage:
curl "http://example.com/api/cache/invalidate?pattern=/products/*"
6. Practice Exercises
Exercise 1: Browser Caching
- Set up a static file server
- Configure expires headers:
- Images: 1 year
- CSS/JS: 1 month
- HTML: 1 hour
- Test with browser DevTools (Network tab)
- Verify cache headers
Exercise 2: Proxy Cache
- Set up backend server (Node.js/Python)
- Configure Nginx proxy cache
- Generate traffic and monitor cache hits/misses
- Check cache files in
/var/cache/nginx
Exercise 3: FastCGI Cache for WordPress
- Install WordPress
- Configure FastCGI cache
- Test cache bypass for:
- Logged-in users
- Admin pages
- POST requests
- Measure performance improvement
Exercise 4: Custom Cache Keys
- Set up cache with custom key (include device type)
- Test from mobile and desktop
- Verify different cached versions
Exercise 5: Cache Purging
- Set up proxy cache
- Create purge endpoint
- Test cache purge:
- Manual purge script
- API endpoint
- Verify cache cleared
Exercise 6: Cache Performance Testing
- Set up with and without cache
- Use Apache Bench to benchmark:
# Without cache ab -n 1000 -c 10 http://example.com/With cache
ab -n 1000 -c 10 http://example.com/
- Compare results
7. Troubleshooting
7.1. Cache Not Working
Problem: X-Cache-Status always shows MISS
Diagnosis:
# Check cache directory permissions ls -la /var/cache/nginx/Check cache config
sudo nginx -T | grep cache
Check if cache zone defined
sudo nginx -T | grep keys_zone
Monitor cache files being created
watch -n 1 'ls -lh /var/cache/nginx/proxy/'
Solutions:
# Ensure proper permissions sudo chown -R nginx:nginx /var/cache/nginx/ sudo chmod -R 755 /var/cache/nginx/Verify cache zone
http { proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=my_cache:10m;
server { location / { proxy_pass http://backend; proxy_cache my_cache; # Must match keys_zone name proxy_cache_valid 200 10m; } }
}
7.2. Cache Taking Too Much Disk Space
Problem: /var/cache/nginx fills up disk
Diagnosis:
# Check cache size du -sh /var/cache/nginx/*Find largest cache files
find /var/cache/nginx -type f -exec du -h {} + | sort -rh | head -20
Solutions:
# Set max_size proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=my_cache:10m max_size=1g # Limit to 1GB inactive=60m; # Purge inactive filesOr use tmpfs (RAM disk)
Add to /etc/fstab:
tmpfs /var/cache/nginx tmpfs defaults,size=512M 0 0
7.3. Stale Content Being Served
Problem: Old content is served despite updates
Solutions:
# Clear cache manually sudo rm -rf /var/cache/nginx/proxy/* sudo systemctl reload nginxOr implement cache versioning
location / { proxy_pass http://backend; proxy_cache my_cache;
# Add version to cache key proxy_cache_key "$scheme$host$request_uri$http_x_app_version";
}