FastCGI Cache Purge
WordPress FastCGI cache purging with automatic and manual controls.
Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/rawhasan/wordpress-fastcgi-cache-purge/archive/refs/heads/main.zipWordPress Nginx FastCGI Cache + Safe Purge
Created by ChatGPT for Raw Hasan
Date: 25 October 2026
Purpose: Reusable setup for WordPress on Ubuntu, Nginx, and PHP-FPM, including automatic cache invalidation and a manual WordPress Admin purge button.
Scope: A reusable adaptation of a configuration tested on a live development installation. This public version uses generic paths and requires site-specific configuration and testing. It is not a drop-in one-command installer. Do not commit credentials, private keys, database passwords, production IPs, or real customer data.
Start here: a simple explanation
This is a three-part system. Think of the Nginx cache as a storage room holding ready-made copies of your website pages. When a visitor arrives, Nginx can hand over a saved page instead of asking WordPress to build it again. When you edit a published page, the old copy needs to be removed.
- Nginx creates and stores the cached pages in a folder on the server.
- A purge helper is a small Linux script that empties only that site's cache folder.
- The WordPress plugin asks the helper to run after published content changes or when an administrator clicks Purge Nginx Cache.
Visitor → Nginx → saved page (fast)
WordPress page updated → plugin → purge helper → delete saved pages
Next visitor → Nginx creates fresh saved pages
Where does /usr/local/sbin/purge-example-cache come from?
You create it during installation. It is not included in Ubuntu, Nginx, or WordPress under that name.
This repository includes a file named purge-wordpress-fastcgi-cache. Copy it onto your server, give the copy a site-specific name, and tell it which cache folder to clear:
File supplied in this repository: purge-wordpress-fastcgi-cache
↓ copy and rename
Installed file on the server: /usr/local/sbin/purge-example-cache
↓ configured to clear
Cache folder on the server: /sites/example.com/cache
The folder /usr/local/sbin/ is a normal Linux location for administrator-managed scripts. Do not type the helper path into wp-config.php until you have actually installed that helper.
What is wp-config.php and why edit it?
wp-config.php is WordPress's settings file. It already exists on your website. Do not replace it. We add one line telling the plugin where its helper lives:
define('RH_FASTCGI_PURGE_HELPER', '/usr/local/sbin/purge-example-cache');
In plain English: “WordPress, when you need to clear the page cache, run the script at this location.”
For this guide, open /sites/example.com/public/wp-config.php and insert the line before the line that loads wp-settings.php. You can usually place it just above the /* That's all, stop editing! Happy publishing. */ comment. Keep every other setting unchanged. Back up wp-config.php before editing it.
This line does not create the helper, choose the cache folder, or set the Nginx cache zone. Those are separate setup steps.
The three names that must match
| Item | Example | What it does |
|---|---|---|
| Nginx cache zone | example_cache |
Internal name Nginx uses to identify the site's cache |
| Nginx cache folder | /sites/example.com/cache |
Folder containing saved page files; the helper must clear this exact folder |
| Installed purge helper | /usr/local/sbin/purge-example-cache |
Script called by WordPress; its exact path must match both wp-config.php and the sudoers rule |
You never enter the Nginx zone name in WordPress.
Before you begin: example names
The instructions below use imaginary values. Replace them for your site:
| Example in this guide | Replace with |
|---|---|
example.com |
Your website's domain |
siteuser |
The Linux account running that site's PHP-FPM process |
www-data |
The Nginx worker account, if different |
example_cache |
A unique cache-zone name for this site |
/sites/example.com/cache |
This site's actual cache directory |
/usr/local/sbin/purge-example-cache |
The unique helper path you choose for this site |
You need SSH administrator (sudo) access. If you have never edited Nginx or sudoers before, do this on a staging server first, or ask a server administrator to perform the Linux steps. A mistake in a cache directory path or sudoers file can cause problems.
Installation overview: six steps
- Set up Nginx caching: choose a cache folder and a unique cache-zone name for the website.
- Create the cache folder: make sure the Nginx worker can access it.
- Install the purge helper: copy
purge-wordpress-fastcgi-cacheto/usr/local/sbin/purge-example-cache, and edit itsCACHE_DIRsetting to match the Nginx cache folder. - Grant limited permission: add a sudoers rule allowing only the site's PHP-FPM account to run that exact helper as the Nginx worker.
- Install and connect WordPress: copy
fastcgi-cache-purge.phptowp-content/mu-plugins/and add the one-lineRH_FASTCGI_PURGE_HELPERsetting towp-config.php. - Test: confirm cache
MISS → HIT, logged-in bypass, automatic purge on published changes, and the manual toolbar button.
The detailed commands are in sections 1–5 below. Use the same example helper path throughout the guide; do not mix it with the repository file's original name.
Why this was created
Nginx FastCGI page caching reduces repeat PHP-FPM and MySQL work for anonymous WordPress visitors. The standard WordPress Nginx cache plugin could not reliably remove cache files: Nginx cache directories were owned by the Nginx worker and could be inaccessible to the PHP-FPM user. Giving PHP broad filesystem permissions would weaken isolation. A restricted helper, invoked through an exact sudoers allowlist, solves this while retaining the Nginx worker's ownership of cache files.
The setup also provides automatic invalidation after public content changes and an administrator-only Purge Nginx Cache toolbar action. This avoids relying on a separate caching plugin with incompatible filesystem assumptions.
Components
fastcgi-cache-purge.php: WordPress must-use plugin. Automatic hooks, toolbar link, nonce/capability checks, manual purge notices.purge-wordpress-fastcgi-cache: executable shell helper. Removes regular files and empty subdirectories inside one fixed cache directory, without removing its root.- Nginx FastCGI cache zone and site-specific PHP location.
- Per-site
RH_FASTCGI_PURGE_HELPERsetting inwp-config.php; no edits to the shared MU plugin are required. - Exact sudoers rule permitting the site's PHP-FPM account to execute only its purge helper as the Nginx worker account.
Request and purge flow
Anonymous GET → Nginx → cache HIT → cached response
↳ cache MISS → PHP-FPM/WordPress → cache populated
Logged-in/admin/API/query request → bypass Nginx page cache → PHP-FPM
Published content update → WordPress hook → MU plugin → sudo allowlist
→ helper as www-data → delete cache entries → next request MISS
Administrator toolbar click → nonce + capability check → same helper
→ redirect to dashboard with success/error notice
Prerequisites
- Ubuntu server with Nginx, PHP-FPM, WordPress, and a working HTTPS virtual host.
- A dedicated PHP-FPM Unix account per site (examples below use
siteuser). - Nginx worker account
www-data(verify withpsandnginx -T; change if different). sudo,find,wp-cli(recommended), and PHPexec()enabled in the actual PHP-FPM pool.- Root/sudo SSH access. Backup and test your existing Nginx site file before editing.
- A dedicated cache directory and cache zone per website. Never point two sites at the same cache directory.
Commands below are examples; replace example.com, siteuser, PHP socket, cache zone, and paths consistently. Run on a staging server first.
How the site selects its cache
The WordPress plugin does not set or reference the Nginx cache zone name. Nginx associates the zone with a cache directory; the WordPress plugin calls a site-specific helper, and that helper clears only its configured directory.
| Setting | Site A example | Site B example |
|---|---|---|
Nginx keys_zone |
site_a_cache:20m |
site_b_cache:20m |
Nginx fastcgi_cache |
site_a_cache |
site_b_cache |
| Cache directory | /sites/site-a.example/cache |
/sites/site-b.example/cache |
| Root-owned purge helper | /usr/local/sbin/purge-site-a-cache |
/usr/local/sbin/purge-site-b-cache |
WordPress RH_FASTCGI_PURGE_HELPER |
/usr/local/sbin/purge-site-a-cache |
/usr/local/sbin/purge-site-b-cache |
Keep one unchanged copy of fastcgi-cache-purge.php in each site's mu-plugins directory. Configure the helper path in each site's wp-config.php and make its sudoers allowlist match exactly. Do not expose a cache directory or helper path through a WordPress Admin option or request parameter.
1. Configure a site-specific FastCGI cache
The fastcgi_cache_path directive must be in Nginx's http context (typically a file included from within http {}), not inside server {}:
fastcgi_cache_path /sites/example.com/cache levels=1:2 keys_zone=example_cache:20m inactive=60m;
Create the cache root with suitable permissions for the Nginx worker, e.g.:
sudo install -d -o www-data -g www-data -m 0750 /sites/example.com/cache
Ensure www-data can traverse /sites and /sites/example.com without granting access to unrelated site data. Use narrowly scoped directory permissions or ACLs where required. The PHP-FPM user does not need write access to the Nginx cache directory.
In the HTTPS site's server {} block, configure the cache behavior. Integrate with existing PHP location and security includes; do not add a duplicate location ~ \.php$ block:
# Example inside server {}:
set $skip_cache 0;
if ($request_method = POST) { set $skip_cache 1; }
if ($query_string != "") { set $skip_cache 1; }
if ($request_uri ~* "/wp-admin/|/wp-json/|/xmlrpc.php|/wp-.*\.php|/feed/|index.php|sitemap(_index)?\.xml|/cart/|/checkout/|/my-account/") { set $skip_cache 1; }
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|edd_items_in_cart|woocommerce_items_in_cart") { set $skip_cache 1; }
add_header Fastcgi-Cache $upstream_cache_status always;
# Add these lines to the site's EXISTING PHP FastCGI location:
# fastcgi_cache_bypass $skip_cache;
# fastcgi_no_cache $skip_cache;
# fastcgi_cache example_cache;
# fastcgi_cache_valid 200 301 60m;
# fastcgi_cache_use_stale error timeout updating invalid_header http_500 http_503;
# fastcgi_cache_background_update on;
Critical: Do not set fastcgi_ignore_headers Cache-Control Expires Set-Cookie;. Respect upstream instructions to prevent caching and session cookies. The example bypass patterns are a baseline, not a guarantee for every WordPress plugin. Audit membership, forms, checkout, logged-in content, authentication, custom REST routes, and other dynamic endpoints before enabling a long TTL. fastcgi_cache_valid sets validity only when upstream headers do not override it; inactive is an eviction period, not a guaranteed TTL.
If your site uses a different PHP location structure (named locations, try_files, proxying, etc.), adapt the directives to the actual handler. Do not blindly replace your current vhost.
Validate and reload:
sudo nginx -t && sudo systemctl reload nginx
2. Install the restricted purge helper
- Download or clone this repository onto the server. From the directory containing
purge-wordpress-fastcgi-cache, install a site-specific copy with the command below. - Set the helper's
CACHE_DIRto exactly the cache folder configured in Nginx. These commands use the example values from above:
sudo install -o root -g root -m 0755 purge-wordpress-fastcgi-cache /usr/local/sbin/purge-example-cache
sudo sed -i "s|^CACHE_DIR=.*|CACHE_DIR='/sites/example.com/cache'|" /usr/local/sbin/purge-example-cache
sudo grep '^CACHE_DIR=' /usr/local/sbin/purge-example-cache
Expected final line: CACHE_DIR='/sites/example.com/cache'.
Explanation: install copies the repository script to its permanent location and sets its owner and permissions. sed edits the installed copy, not the repository file. grep lets you check the result. Never use a cache folder that belongs to a different website.
The helper checks that the cache root is a directory and not a symlink, rejects symlinks found inside it, deletes regular files, then deletes empty subdirectories. It deliberately preserves the cache root and its permissions. This is a full-zone filesystem purge, not selective per-URL invalidation. Do not edit it to run rm -rf on a user-supplied path.
Security considerations
The helper is designed for an ordinary dedicated cache directory controlled by administrators and the Nginx worker. It is not hardened against a malicious concurrent filesystem writer swapping paths during a purge; for a high-threat multi-tenant environment, use a stronger privileged service design. Keep the cache directory and its parent path outside the WordPress-writable tree where practical. Never let the WordPress account modify the helper or sudoers file.
3. Permit only the required sudo action
Run sudo visudo -f /etc/sudoers.d/example-fastcgi-purge and add one line (replace siteuser with the actual PHP-FPM user):
siteuser ALL=(www-data) NOPASSWD: /usr/local/sbin/purge-example-cache
Then:
sudo chmod 0440 /etc/sudoers.d/example-fastcgi-purge
sudo visudo -cf /etc/sudoers.d/example-fastcgi-purge
sudo -u siteuser sudo -n -u www-data /usr/local/sbin/purge-example-cache
Expected: Cache purge completed.
For multiple sites, create one helper per site, with a unique root-owned executable path, cache directory, and sudoers rule. Avoid a generic script that accepts arbitrary paths from the PHP-FPM user.
4. Install the WordPress MU plugin
sudo install -d -o siteuser -g siteuser -m 0750 /sites/example.com/public/wp-content/mu-plugins
sudo install -o siteuser -g siteuser -m 0640 fastcgi-cache-purge.php /sites/example.com/public/wp-content/mu-plugins/fastcgi-cache-purge.php
Required for this multi-site example: define the site-specific helper path in wp-config.php, before the line that loads wp-settings.php:
define('RH_FASTCGI_PURGE_HELPER', '/usr/local/sbin/purge-example-cache');
The helper was installed in section 2, and the matching sudoers rule was created in section 3. The plugin has a built-in fallback helper path, but do not rely on it for multi-site setups: explicitly set RH_FASTCGI_PURGE_HELPER for every website. This constant does not configure the Nginx zone name; that remains in Nginx.
sudo php -l /sites/example.com/public/wp-content/mu-plugins/fastcgi-cache-purge.php
sudo -u siteuser wp plugin list --status=must-use --path=/sites/example.com/public
MU plugins load automatically. The code uses exec() to invoke sudo -n, so failures do not prompt for a password. Failures are written to the PHP error log. The WordPress Admin toolbar item is shown only to users with manage_options; the action also checks the same capability and a WordPress nonce. Manual purges redirect to the dashboard with a success/error notice.
Automatic hooks
save_poston a published post/page/custom post type, excluding revisions and autosaves.transition_post_statuswhen entering or leavingpublish.- Post deletion/trashing, comment actions, navigation menu updates, and theme switching.
The plugin avoids multiple successful automatic purges in one PHP request. It does not automatically detect every sitewide option, theme customization, plugin setting, asset, or direct database change. Use the manual purge button after such changes. Some deletion/comment hooks are intentionally broad and may purge more often than necessary. Cache clearing is site-wide, not limited to affected URLs.
5. Test the actual web flow
Warm the cache and inspect status:
for i in 1 2; do curl -sS -o /dev/null -D - https://example.com/ | grep -iE '^(HTTP/|Fastcgi-Cache:)'; done
Look for MISS then HIT; STALE and UPDATING can appear when stale-while-revalidate is active. With an existing warm cache, HIT immediately is normal.
Test a simulated login cookie (does not authenticate):
curl -sS -o /dev/null -D - -H 'Cookie: wordpress_logged_in_test=1' https://example.com/ | grep -iE '^(HTTP/|Fastcgi-Cache:)'
Expected: BYPASS.
Test exclusions (/wp-admin/, /wp-json/, /feed/, /?s=test) and ensure none returns HIT. Then:
- Update a published page in WordPress Admin; immediately check for
MISSthenHIT. - Save an unpublished draft; check that the already-warm public homepage remains
HIT. - Click Purge Nginx Cache in the Admin toolbar; confirm the green success notice and check
MISSthenHIT. - Check PHP-FPM and Nginx error logs for failed helper calls.
If the first request after a purge is already HIT, a concurrent request may have rebuilt the cache. To conclusively test, coordinate with traffic or inspect cache files before and after purging.
6. Manual SSH purge and operations
sudo -u siteuser sudo -n -u www-data /usr/local/sbin/purge-example-cache
No Nginx reload is required for purging. Keep Nginx reloads for configuration changes. If using a CDN, note that purging origin FastCGI cache does not purge CDN edge cache; clear the CDN separately if needed.
Troubleshooting
| Symptom | What to check |
|---|---|
BYPASS on public pages |
Cookies, query strings, path exclusions, upstream Cache-Control |
No HIT |
Response Set-Cookie, upstream cache headers, missing cache directives, cache directory permissions |
| WordPress reports purge failure | PHP-FPM exec(), sudoers exact path, helper permissions, PHP error log |
sudo: a password is required |
NOPASSWD rule, actual PHP-FPM user, helper path |
Permission denied |
Parent directory traversal and Nginx worker ownership |
Cache contains symbolic links |
Investigate unexpected cache contents; do not weaken the guard blindly |
| Changes still look stale | CDN cache, browser cache, WordPress object cache, missed purge hook |
STALE or UPDATING |
Expected with background updates and stale-while-revalidate |
7. Adding a second website
Repeat the installation separately for each site. For example, second.example could use:
Nginx zone: second_cache
Nginx folder: /sites/second.example/cache
Installed helper:/usr/local/sbin/purge-second-cache
WordPress line: define('RH_FASTCGI_PURGE_HELPER', '/usr/local/sbin/purge-second-cache');
Copy the repository's same purge-wordpress-fastcgi-cache script again, this time to /usr/local/sbin/purge-second-cache; change its CACHE_DIR to /sites/second.example/cache; and authorize the second site's PHP-FPM user in its own sudoers rule. Install the same WordPress plugin on the second site. The sites must not share a cache folder or purge helper.
8. Migration and security checklist
- [ ] Verify PHP-FPM pool user and Nginx worker user.
- [ ] Configure separate Nginx cache zone and directory for each site.
- [ ] Preserve existing TLS, redirects, HTTP/2 or HTTP/3, and security rules.
- [ ] Respect
Cache-Control,Expires, andSet-Cookie. - [ ] Audit custom dynamic routes and authentication/session cookies.
- [ ] Install root-owned, site-specific helper and exact sudoers allowlist.
- [ ] Install MU plugin and confirm
php -land WordPress registration. - [ ] Test caching, bypass, automatic purge, manual purge, and error logs.
- [ ] Document any site-specific hooks required for plugin settings or custom content.
- [ ] Keep a rollback copy of the prior Nginx config and purge setup.
- [ ] Deploy on staging first; never assume that cache exclusions are universal.
Rollback
Remove/rename the MU plugin to disable its hooks and toolbar action. Restore the previous Nginx vhost (or remove the FastCGI cache directives), then run nginx -t and reload. Remove the site's sudoers allowlist and helper when no longer needed. Do not delete the cache root until Nginx no longer references it.
Attribution
Created by ChatGPT for Raw Hasan. The initial solution was developed and validated iteratively through WordPress Admin, WP-CLI, PHP-FPM, and Nginx tests. This repository version is generalized for reuse; review and adapt it before production deployment.