2026年11月12日
2026年11月12日
WordPress Server Cache: Nginx FastCGI Cache Setup and Apache Options
This article is a localized edition of the Japanese original. Read the Japanese original
A server cache (page cache) stores the HTML that WordPress generates on the server, and the next time the same page is requested it is returned without going through PHP and the database. Skipping page generation shortens the time to first byte (TTFB) and lowers server load when traffic spikes.
This article focuses on configuring the FastCGI cache when you run Nginx with PHP-FPM, for example on a VPS, and then sorts out how to approach caching on Apache. The Nginx directives were checked against the official nginx.org documentation, and purging on update against the Nginx Helper plugin's page and source code, as of November 2026.
Decide this first
The most important thing with a server cache is to never store a personalized page and serve it to someone else. Exclude the following from the cache:
- Pages for logged-in users (including the admin bar and personalized content)
- Pages for people who have left a comment (their name and email are filled in the form)
- Password-protected posts
- The admin area,
wp-login.php,xmlrpc.php, the REST API and feeds - POST requests and URLs with a query string
- Cart, checkout and My Account pages in WooCommerce and similar plugins
The other thing to decide is how to delete stored HTML when you update a post. Without a way to purge it, the old content keeps showing until the cache expires.
Configure the Nginx FastCGI cache
The example below uses Nginx and PHP-FPM for PHP 8.3 on Ubuntu 24.04. Socket paths and config file locations differ by distribution and PHP version.
Define where the cache is stored
fastcgi_cache_path must go in the http block. On Ubuntu, files under /etc/nginx/conf.d/ are included in the http block, so create a new file there.
# /etc/nginx/conf.d/fastcgi-cache.conf
fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2 keys_zone=WORDPRESS:100m inactive=60m max_size=1g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
| Setting | Meaning |
|---|---|
levels=1:2 |
Spreads the cached files across a two-level directory tree |
keys_zone=WORDPRESS:100m |
The cache name and the size of the shared memory that holds the keys (1 MB holds about 8,000 keys) |
inactive=60m |
Cached items not accessed for this long are removed, even if they haven't expired |
max_size=1g |
The maximum size of the cache on disk |
fastcgi_cache_key |
What counts as the same page: here the scheme, method, host name and URL |
Create the cache directory in advance.
sudo mkdir -p /var/cache/nginx/wordpress
sudo chown www-data:www-data /var/cache/nginx/wordpress
Decide when not to cache
In your site's server block, write the conditions under which the cache is not used. Requests where $skip_cache becomes 1 are neither served from the cache nor stored.
# /etc/nginx/sites-available/example.com (inside the server block)
set $skip_cache 0;
# POST requests and URLs with a query string
if ($request_method = POST) {
set $skip_cache 1;
}
if ($query_string != "") {
set $skip_cache 1;
}
# Admin, login, REST API, feeds, sitemaps and similar
if ($request_uri ~* "/wp-admin/|/xmlrpc\.php|wp-.*\.php|/wp-json/|/feed/|index\.php|sitemap(_index)?\.xml") {
set $skip_cache 1;
}
# WooCommerce cart, checkout and My Account (if you use it)
if ($request_uri ~* "/cart/|/checkout/|/my-account/") {
set $skip_cache 1;
}
# Logged-in users, commenters, password-protected posts, visitors with items in the cart
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|woocommerce_items_in_cart|wp_woocommerce_session_") {
set $skip_cache 1;
}
If you changed the slugs of the WooCommerce pages, adjust the paths to match.
Use the cache in the PHP location
Add the cache directives to the PHP location in the same server block.
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_cache WORDPRESS;
fastcgi_cache_valid 200 301 302 60m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache_use_stale error timeout updating http_500 http_503;
fastcgi_cache_background_update on;
fastcgi_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
}
| Directive | Meaning |
|---|---|
fastcgi_cache |
The cache to use (the name set in keys_zone) |
fastcgi_cache_valid |
How long to keep responses, per status code |
fastcgi_cache_bypass |
When the value is neither empty nor 0, don't serve from the cache |
fastcgi_no_cache |
When the value is neither empty nor 0, don't store the response |
fastcgi_cache_use_stale |
Serve an expired cached copy when PHP errors out or while it is being updated |
fastcgi_cache_background_update |
Serve the expired copy while fetching fresh content in the background (used together with updating in use_stale) |
fastcgi_cache_lock |
Pass only one of several simultaneous requests for the same page to PHP |
add_header X-Cache-Status |
Lets you see in the response headers whether the page came from the cache (HIT) and so on |
By default, Nginx does not store a response that has a Set-Cookie header or that forbids caching with Cache-Control: no-cache and the like. WordPress adds headers that forbid caching to responses for logged-in users, but don't rely on that; exclude them with $skip_cache as above.
If you put add_header in a location, any add_header directives in the server block (security headers, for example) are no longer applied in that location. Repeat the headers you need inside the location as well.
Test and apply the configuration
sudo nginx -t
sudo systemctl reload nginx
Apply it only after nginx -t prints "syntax is ok" and "test is successful".
Purge the cache when content changes
Stock Nginx has no way to delete cache entries by URL. For WordPress, the usual approach is the Nginx Helper plugin (available on WordPress.org). It offers two purge methods.
| Purge Method | What it needs |
|---|---|
A GET request to /purge/URL |
The third-party Nginx module ngx_cache_purge and configuration that uses it |
| Delete local server cache files (deletes the cache files directly) | No extra module. WordPress and the cache must be on the same server |
Here we use "Delete local server cache files", which needs no extra module. According to Nginx Helper, this method assumes levels=1:2 together with fastcgi_cache_key "$scheme$request_method$host$request_uri". The configuration above matches those requirements.
First, add the cache location to wp-config.php (above the /* That's all, stop editing! */ line).
define( 'RT_WP_NGINX_HELPER_CACHE_PATH', '/var/cache/nginx/wordpress' );
Then configure the plugin.
- Under Plugins → "Add New" (プラグインの新規追加 in the Japanese UI), search for "Nginx Helper", install it and activate it.
- Go to "Settings" (設定) → "Nginx Helper", check "Enable Purge", choose the nginx FastCGI cache as the caching method, select "Delete local server cache files" under "Purge Method", and save.
- On the same screen, choose when to purge (when a post is published or updated, when a comment is approved, whether to purge the home page and archives too, and so on).
The PHP-FPM user must be able to delete the cache files that Nginx creates. In Ubuntu's default setup both run as www-data, so this works as is.
To clear the whole cache manually, use "Purge Cache" in the admin bar or run the following command on the server.
sudo find /var/cache/nginx/wordpress -type f -delete
Check that it works
Fetch the same page twice as a logged-out visitor. curl -I sends a HEAD request, and because the method is part of the cache key it would be cached separately from GET, so fetch the body with GET and print only the headers.
curl -s -o /dev/null -D - https://example.com/ | grep -i x-cache-status
curl -s -o /dev/null -D - https://example.com/ | grep -i x-cache-status
If the first request shows MISS and the second HIT, the page is being stored and served from the cache.
| Value | Meaning |
|---|---|
MISS |
Not in the cache, so PHP generated it |
HIT |
Served from the cache |
BYPASS |
Matched the fastcgi_cache_bypass condition |
EXPIRED |
Had expired, so PHP regenerated it |
STALE / UPDATING |
An expired copy was served (because of fastcgi_cache_use_stale) |
If it stays MISS no matter how many times you fetch it, print all headers with curl -s -o /dev/null -D - URL and check for Set-Cookie or Cache-Control: no-cache. If a plugin that uses sessions issues a cookie, the page is not stored.
If a browser that is logged in sees BYPASS, the exclusion rules are working.
On Apache
Apache's mod_cache is a general-purpose mechanism that decides what to store by HTTP caching rules; it cannot switch pages based on WordPress login cookies. A stored page meant for anonymous visitors may even be returned to a logged-in user. Configuration examples and caveats are in "Apache cache settings (mod_cache)".
For WordPress on Apache, a page cache plugin that understands login and commenter cookies is, in practice, easier to handle. On a hosting service that runs LiteSpeed Web Server, you can use LiteSpeed Cache, which works with the server's cache. For choosing a plugin, see "How to compare and choose a WordPress cache plugin".
About WP_CACHE and object caching
define( 'WP_CACHE', true ); in wp-config.php tells WordPress to load wp-content/advanced-cache.php, which page cache plugins such as WP Super Cache use. The Nginx FastCGI cache runs outside WordPress, so it doesn't need this setting.
An object cache, which stores the results of database queries, is a separate mechanism from the page cache. If you also want faster logged-in pages and admin screens, which are excluded from the page cache, add an object cache such as Redis. The setup is covered in "How to speed up WordPress object caching with Redis".
Cautions
- After changing cache settings, test both logged out and logged in. If logged-in pages are served from the cache, the admin bar and personal information may be shown to other people.
- If you have members-only pages or pages whose content varies per visitor, add their paths and cookies to the exclusion rules too.
- A long
fastcgi_cache_validmeans that if purging on update fails, stale content stays around for a long time. Start short, confirm that updates purge the cache, and then lengthen it.
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)