Nginx FastCGI Cache Speed up WordPress on VPS
Nginx FastCGI Cache is a caching system where Nginx stores the output results from PHP-FPM on disk and then sends the response on the next request directly without touching PHP or MySQL at all. For WordPress sites with heavy traffic, enabling FastCGI Cache reduces CPU load by 60-90% and brings Time to First Byte (TTFB) down to below 10ms.
How FastCGI Cache Works
Normally every incoming request is passed by Nginx to PHP-FPM for processing, fetches from the database, generates HTML, and sends it back — taking 100–500ms per request. When FastCGI Cache is enabled, Nginx:
- Checks if this request already has a cache entry
- If yes → sends the cached response immediately (skips PHP and DB)
- If no or expired → passes to PHP-FPM and stores the result in cache
Configure FastCGI Cache in nginx.conf
# Add to http{} block of /etc/nginx/nginx.conf
fastcgi_cache_path /var/cache/nginx/fastcgi_cache
levels=1:2
keys_zone=WORDPRESS:100m
max_size=1g
inactive=60m
use_temp_path=off;
# Create directory for cache
sudo mkdir -p /var/cache/nginx/fastcgi_cache
sudo chown www-data:www-data /var/cache/nginx/fastcgi_cache
Configure Server Block for WordPress
# /etc/nginx/sites-available/example.com
# Define cache key and bypass rules in server block
set $skip_cache 0;
# Do not cache POST requests
if ($request_method = POST) { set $skip_cache 1; }
# Do not cache when there is query string (e.g., ?s= search)
if ($query_string != "") { set $skip_cache 1; }
# Do not cache admin pages and login
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") {
set $skip_cache 1;
}
# Do not cache when user is logged in or has comment cookie
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
set $skip_cache 1;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# FastCGI Cache settings
fastcgi_cache WORDPRESS;
fastcgi_cache_valid 200 60m;
fastcgi_cache_use_stale error timeout invalid_header updating http_500;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
# Add header to debug if cache is HIT or MISS
add_header X-FastCGI-Cache $upstream_cache_status;
}
Purge Cache When Publishing or Updating Posts
WordPress must clear cache when publishing new posts. Use the Nginx Helper plugin combined with ngx_cache_purge module:
# Check if purge module exists
nginx -V 2>&1 | grep purge
# If not present, install nginx-extras
sudo apt install nginx-extras
# Add to server block
location ~ /purge(/.*) {
fastcgi_cache_purge WORDPRESS "$scheme$request_method$host$1";
allow 127.0.0.1;
deny all;
}
# Purge cache manually (clear all)
sudo rm -rf /var/cache/nginx/fastcgi_cache/*
sudo systemctl reload nginx
Proxy Cache for Node.js / Backend Apps
If you use Nginx as a reverse proxy for a backend app, you can also cache responses using Proxy Cache:
# In http{} block
proxy_cache_path /var/cache/nginx/proxy_cache
levels=1:2
keys_zone=BACKEND:50m
max_size=500m
inactive=30m;
# In server block
location /api/ {
proxy_pass http://localhost:3000;
proxy_cache BACKEND;
proxy_cache_valid 200 5m;
proxy_cache_key "$host$request_uri";
proxy_cache_bypass $http_pragma $http_authorization;
add_header X-Proxy-Cache $upstream_cache_status;
}
curl -I https://example.com/ and check the X-FastCGI-Cache: HIT header. If you get MISS or BYPASS every time, the cache is not working — check your $skip_cache conditions
Verify Nginx Cache is Working Correctly
# View cache status from response header
curl -sI https://example.com/ | grep -i cache
# Test 3 times in a row — first should be MISS, second and third should be HIT
for i in 1 2 3; do curl -sI https://example.com/ | grep "X-FastCGI-Cache"; done
# View cache size stored
du -sh /var/cache/nginx/fastcgi_cache/
# Check error log for any issues
tail -50 /var/log/nginx/error.log | grep -i cache
ab -n 1000 -c 10 https://example.com/ (Apache Bench) to test throughput before/after enabling cache — you should see requests/sec increase by at least 3-5 times
Common Issues & Troubleshooting
Error: "permission denied" when Nginx writes cache
The Nginx worker process must have write permission to the cache directory:
# Check the user that nginx worker runs as
ps aux | grep nginx
# The line that is not root is the worker — usually www-data
# Fix permissions
sudo chown -R www-data:www-data /var/cache/nginx/
sudo chmod -R 755 /var/cache/nginx/
nginx -t && systemctl reload nginx
Cache does not clear when publishing new post in WordPress
You must install a plugin that supports Nginx FastCGI Cache purge such as "Nginx Helper" and configure the purge URL to match your server:
# Install wp-cli then install plugin via command line
wp plugin install nginx-helper --activate
# Check if purge endpoint works
curl -X PURGE https://example.com/
# Should return HTTP 200 Successful purge
Website shows old data (stale content) after update
The cache TTL that is set may be too long, or bypass conditions do not cover logged-in users:
# Clear all cache manually
sudo find /var/cache/nginx/fastcgi_cache/ -type f -delete
sudo systemctl reload nginx
# Reduce TTL to 15 minutes for sites that update frequently
fastcgi_cache_valid 200 15m;
Nginx not caching at all, every request is BYPASS
This happens when $skip_cache is set to 1 all the time. Check if a cookie or query string is causing every request to be skipped:
# Add debug header to see skip_cache value
add_header X-Skip-Cache $skip_cache;
# Test a clean request (no cookie)
curl -sI https://example.com/ --cookie "" | grep -i "cache\|skip"
Frequently Asked Questions
How is FastCGI Cache different from Redis Object Cache?
FastCGI Cache stores the entire HTML output of the page (full-page cache) on disk — completely skipping PHP and MySQL. Redis Object Cache stores database query results in RAM — still goes through PHP but reduces DB load. FastCGI Cache is much faster for anonymous visitors, while Redis is better for logged-in users since FastCGI cache cannot cache those requests.
How large should the cache size be?
For typical WordPress sites, 1-2GB is sufficient. In fastcgi_cache_path set max_size=2g. Nginx will automatically delete old files when it approaches the limit. Check actual disk usage with du -sh /var/cache/nginx/
Can FastCGI Cache be used for sites that need personalization (shopping cart, login)?
Yes, but you must set up bypass conditions correctly. For WooCommerce add a bypass when the woocommerce_cart_hash or woocommerce_items_in_cart cookie is present to prevent users from seeing other shoppers' carts.