Nginx Microcaching: Full-Page Cache for WordPress and Dynamic Sites Without Varnish
Nginx microcaching stores the output of PHP pages for a short duration — typically 1 to 60 seconds — and serves that cached HTML for subsequent requests during the cache window. A WordPress page that normally takes 200ms of PHP execution takes under 1ms when served from Nginx’s memory cache. During traffic spikes (viral content, product launches), microcaching transforms a server that would collapse under 100 concurrent PHP requests into one that serves thousands per second from cache.
Microcaching vs Varnish
- Nginx microcaching: Built into Nginx — no extra software, minimal RAM overhead, 1-second cache TTL handles high traffic while keeping content fresh
- Varnish: Dedicated cache server, more sophisticated VCL rules, higher cache TTL (minutes to hours), requires separate process
- Choose microcaching: For single VPS deployments where simplicity matters, or as a first caching layer before considering Varnish
Step 1: Create Cache Directory
<code">sudo mkdir -p /var/cache/nginx sudo chown www-data:www-data /var/cache/nginx
Step 2: Configure Nginx Cache Zone (http block)
<code">sudo nano /etc/nginx/nginx.conf
Add inside the http { } block:
<code">http {
# Define the cache zone
# keys_zone=microcache: = zone name (must match proxy_cache directive)
# 10m = 10 MB for cache keys (holds ~80,000 keys)
# inactive=60s = evict items not accessed in 60 seconds
# max_size=1g = maximum total cache size on disk
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=microcache:10m
max_size=1g
inactive=60s
use_temp_path=off;
# ... other http settings
}
Step 3: WordPress Site Configuration
<code">sudo nano /etc/nginx/sites-available/wordpress
<code">fastcgi_cache_path /var/cache/nginx/fastcgi
levels=1:2
keys_zone=wordpress_cache:10m
max_size=512m
inactive=60s
use_temp_path=off;
# Map to detect conditions where we should skip the cache
map $request_method $no_cache_method {
default 0;
POST 1; # Never cache POST requests
}
map $http_cookie $no_cache_cookie {
default 0;
"~*wordpress_logged_in" 1; # Skip for logged-in users
"~*comment_author" 1;
"~*woocommerce_cart_hash" 1; # Skip WooCommerce cart
"~*woocommerce_items_in_cart" 1;
}
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
root /var/www/wordpress;
index index.php;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# Cache control variables
set $skip_cache 0;
# Skip cache for POST requests
if ($request_method = POST) { set $skip_cache 1; }
# Skip cache for logged-in users
if ($http_cookie ~* "wordpress_logged_in|comment_author|woocommerce_cart") {
set $skip_cache 1;
}
# Skip cache for admin and login pages
if ($request_uri ~* "(/wp-admin/|/wp-login.php|/xmlrpc.php)") {
set $skip_cache 1;
}
# Skip cache for query strings (pagination, search, etc.)
if ($query_string != "") { set $skip_cache 1; }
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTPS on;
# Cache configuration
fastcgi_cache wordpress_cache;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 301 302 60s; # Cache 200/301/302 responses for 60 seconds
fastcgi_cache_bypass $skip_cache; # Don't serve from cache when $skip_cache = 1
fastcgi_no_cache $skip_cache; # Don't store in cache when $skip_cache = 1
fastcgi_cache_use_stale error timeout updating; # Serve stale while refreshing
# Add debug headers (remove in production)
add_header X-Cache-Status $upstream_cache_status;
}
# Cache static files at browser level
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff2|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
}
<code">sudo nginx -t && sudo systemctl reload nginx
Step 4: Test Cache is Working
<code"># X-Cache-Status header shows cache status curl -I https://yourdomain.com 2>/dev/null | grep -i "x-cache" # First request: X-Cache-Status: MISS # Second request: X-Cache-Status: HIT # Confirm cache files are being created ls -la /var/cache/nginx/fastcgi/ du -sh /var/cache/nginx/
Step 5: Benchmark the Improvement
<code"># Before microcaching ab -n 200 -c 20 https://yourdomain.com/ # Requests per second: ~50 # Time per request: ~400ms # After microcaching (run after first request primes the cache) ab -n 5000 -c 100 https://yourdomain.com/ # Requests per second: 3,000–8,000 # Time per request: ~1ms
Step 6: Cache Purging
<code"># Manual purge — delete all cache files
sudo find /var/cache/nginx/fastcgi -type f -delete
# Purge on WordPress publish (add to functions.php or a plugin)
add_action('save_post', function($post_id) {
if (wp_is_post_revision($post_id)) return;
// Delete cache files for this post's URL
$url = get_permalink($post_id);
shell_exec("find /var/cache/nginx -type f -delete 2>/dev/null");
});
# Install nginx-cache-purge module for URL-specific purging (optional)
# Or use the Nginx Cache WordPress plugin
Step 7: Microcache for Non-WordPress Applications
<code">location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_cache microcache;
proxy_cache_key "$scheme$request_method$host$request_uri$is_args$args";
proxy_cache_valid 200 10s; # Cache API responses for 10 seconds
proxy_cache_bypass $no_cache_method;
proxy_no_cache $no_cache_method;
proxy_cache_use_stale error timeout updating;
add_header X-Cache-Status $upstream_cache_status;
}
Cache TTL Strategy
- 1–5 seconds: News sites, real-time data, frequently updated content. Still absorbs massive traffic spikes while staying fresh.
- 60 seconds: Blog posts, documentation, product pages that don’t change every minute.
- 5–10 minutes: Long-form content, archives, pages that rarely change. Good for very high traffic sites.
- Avoid: Caching user-specific content, checkout pages, admin interfaces.
Getting Started
Nginx microcaching is built into Nginx — no additional packages, no extra RAM for a separate cache service. A 1 GB Ubuntu VPS at VPS.DO with Nginx microcaching handles the same traffic as a 4 GB VPS without caching. The 512 MB cache zone size in the configuration above stores thousands of cached pages while fitting comfortably within most VPS memory allocations.
Conclusion
Nginx fastcgi microcaching transforms WordPress from a PHP application that collapses under traffic spikes into a system that serves cached pages from memory at thousands of requests per second. The 60-second cache TTL keeps content acceptably fresh while dramatically reducing PHP-FPM and database load. Bypassing the cache for logged-in users, POST requests, and admin pages ensures content accuracy where it matters, while anonymous traffic gets the full benefit of the cache layer.