Nginx FastCGI cache is one of the highest-impact performance optimisations available for a self-hosted WordPress server. Instead of PHP-FPM generating the same HTML response on every request, Nginx caches the first response to disk and serves subsequent requests for the same URL directly from that cache file, completely bypassing PHP and MySQL. On a cache hit the response time drops from hundreds of milliseconds to single-digit milliseconds, and server CPU usage falls dramatically because no PHP processes are spawned. The cache is stored on the server filesystem in a configurable directory. The Nginx configuration requires two parts: a fastcgi_cache_path directive in the http block that defines the cache storage location, key zone name, maximum size, and inactive expiry, and a set of fastcgi_cache directives inside the WordPress server block that activate the cache, define the cache key, set the TTL for 200 responses, and define the bypass conditions. The bypass conditions are critical — logged-in WordPress users, users with shopping carts (WooCommerce), and requests with the ?nocache parameter must always skip the cache so they receive dynamic personalised responses. The cache key is typically a combination of the request scheme, host, and URI. Cache invalidation — purging the cached version when a post is updated — requires either the Nginx Cache Purge module or a WordPress plugin that sends a purge request to Nginx. The server monitoring guide explains how to measure the impact of caching with vmstat and iostat. Pair this with the lazy loading guide for a complete front-end and server-side performance stack.
Problem: Every WordPress page view triggers a full PHP-FPM and MySQL cycle even for identical requests from anonymous visitors, causing high CPU usage and slow response times under traffic.
Solution: Add FastCGI cache configuration to Nginx so anonymous page requests are served from disk without invoking PHP:
# /etc/nginx/nginx.conf — add inside the http {} block
fastcgi_cache_path /var/run/nginx-cache
levels=1:2
keys_zone=WORDPRESS:100m
inactive=60m
max_size=1g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
# /etc/nginx/sites-available/yourdomain.com — inside server {} block
set $skip_cache 0;
# Skip cache for POST requests
if ($request_method = POST) { set $skip_cache 1; }
# Skip cache for URLs with query strings
if ($query_string != "") { set $skip_cache 1; }
# Skip cache for logged-in users and commenters
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
set $skip_cache 1;
}
# Skip cache for WooCommerce cart and checkout pages
if ($request_uri ~* "/cart/|/checkout/|/my-account/") {
set $skip_cache 1;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_cache WORDPRESS;
fastcgi_cache_valid 200 60m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
# Add header to show cache status (HIT or MISS) for debugging
add_header X-FastCGI-Cache $upstream_cache_status;
}
# Purge endpoint for the Nginx Helper plugin
location ~ /purge(/.*) {
fastcgi_cache_purge WORDPRESS "$scheme$request_method$host$1";
}
NOTE: After enabling FastCGI cache, install the Nginx Helper WordPress plugin (free, by rtCamp) and configure it to purge the cache automatically when posts are published or updated. Without cache purging, visitors will see stale content until the inactive timeout expires. To verify the cache is working, add add_header X-FastCGI-Cache $upstream_cache_status; to the PHP location block and check the response headers with curl -I https://yourdomain.com — you should see X-FastCGI-Cache: HIT on the second request. Create the cache directory before restarting Nginx: sudo mkdir -p /var/run/nginx-cache && sudo chown www-data:www-data /var/run/nginx-cache.