PFC Object Cache
Persistent WordPress object cache drop-in powered by PHPFastCache. Supports Redis, Memcached, APCu, Files and more.
by Stoil Dobreff · github.com/sdobreff/pfc-object-cache · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/sdobreff/pfc-object-cache/archive/refs/heads/main.zipPersistent WordPress object cache drop-in powered by PHPFastCache.
Features
- Multiple drivers — Files, Redis, Memcached, APCu, SQLite3, and all other PHPFastCache drivers
- Static cache engine —
PFC\ObjectCache\CacheEngineprovides a fully static API that can be used directly or through theWP_Object_Cachewrapper - Nginx cache purge — Automatically (or manually) purge the nginx FastCGI / proxy cache when the object cache is flushed
- Standalone admin menu — Top-level WordPress admin page with statistics dashboard, driver configuration, and nginx purge settings
- One-click cache management — Flush all caches, flush individual cache groups, or purge the nginx cache
- Multisite-safe — Non-global cache groups are prefixed with the blog ID
- Namespaced & strict — All classes live under the
PFC\ObjectCachenamespace withdeclare(strict_types=1) - CLI / web unified cache — Uses a single explicit cache directory so CLI (WP-CLI, cron) and web requests share the same cache store
- WPCS-compliant — Follows WordPress Coding Standards throughout
- Secure — Every action is protected with
current_user_can('manage_options')+ nonce verification; all input sanitised, all output escaped
Requirements
| Requirement | Minimum |
|---|---|
| PHP | 8.1 |
| WordPress | 6.4 |
| Composer | 2.x |
Installation
1. Install via Composer
cd wp-content/plugins/
git clone https://github.com/your-org/pfc-object-cache.git
cd pfc-object-cache
composer install --no-dev --optimize-autoloader
2. Activate the plugin
Activate PFC Object Cache from Plugins → Installed Plugins in the WordPress admin.
On activation, the plugin copies drop-in/object-cache.php to wp-content/object-cache.php.
3. Configure your driver
Navigate to Object Cache in the WordPress admin menu, choose your preferred driver, and click Save Settings.
Important: After changing the driver, deactivate and reactivate the plugin so the drop-in picks up the new configuration.
Architecture
PFC\ObjectCache\CacheEngine (static) — All cache logic, driver bootstrap
PFC\ObjectCache\NginxCachePurger (static) — Nginx cache directory purging
PFC\ObjectCache\AdminPage (static) — Admin UI, settings persistence
PFC\ObjectCache\CacheManager (static) — Flush / stats helpers for admin
PFC\ObjectCache\DropInInstaller (static) — Activation/deactivation lifecycle
WP_Object_Cache (global) — Thin wrapper delegating to CacheEngine
wp_cache_*() (global) — WordPress API functions
The WP_Object_Cache class in the drop-in is a thin wrapper. All persistent
cache logic lives in CacheEngine, which can also be called directly:
use PFC\ObjectCache\CacheEngine;
CacheEngine::set( 'my_key', $data, 'my_group', 3600 );
$value = CacheEngine::get( 'my_key', 'my_group' );
Nginx Cache Purge
The plugin can automatically purge the nginx FastCGI or proxy cache whenever the object cache is flushed, and also auto-purge individual URLs when content is updated or deleted.
Full Cache Purge (directory-based)
- In Object Cache → Driver Configuration, enable Nginx Cache Purge.
- Set the Nginx Cache Path to the absolute path of your
fastcgi_cache_pathorproxy_cache_pathdirectory (e.g./var/run/nginx-cache). - Save settings.
After this, any "Flush All Caches" action will also recursively delete the nginx cache directory contents. You can also use the Purge Nginx Cache button for a standalone purge.
Tip: Ensure the web-server user (e.g.
www-data) has write access to the nginx cache directory.
Auto-Purge on Content Changes (per-URL)
When a post, comment, or term is created, updated, or deleted, the plugin can automatically send purge requests for the affected URLs (permalink, home page, archives, feeds, etc.) so visitors immediately see fresh content.
This requires the nginx fastcgi_cache_purge module (or the equivalent
proxy_cache_purge) compiled into your nginx build.
1. Nginx configuration
Add a /purge/ location block inside your server {}:
# FastCGI cache zone — adjust path, levels and keys_zone to match your setup.
fastcgi_cache_path /var/run/nginx-cache levels=1:2
keys_zone=WORDPRESS:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
server {
# ... your existing config ...
# Cache-purge endpoint — only accessible from localhost.
location ~ /purge(/.*) {
allow 127.0.0.1;
deny all;
fastcgi_cache_purge WORDPRESS "$scheme$request_method$host$1";
}
}
Key points:
| Directive | Purpose |
|---|---|
fastcgi_cache_key |
Defines how nginx builds the cache key. The purge must reconstruct the same key. |
location ~ /purge(/.*) {} |
Captures the original path via $1 and purges the matching cache entry. |
allow 127.0.0.1; deny all; |
Restricts purge requests to localhost for security. |
Important: The
$schemein the cache key means HTTPS and HTTP entries are stored separately. The plugin preserves the original URL scheme when sending purge requests so the key matches correctly.
2. Plugin settings
- In Object Cache → Driver Configuration → Nginx Cache Purge, enable Nginx Cache Purge (checkbox).
- Set the Purge Server URL to the loopback address and port nginx listens
on, e.g.
http://127.0.0.1:80orhttps://127.0.0.1:443. - Enable Auto-purge nginx cache when content is updated or deleted (checkbox).
- Save settings.
3. How it works
When a post is updated, the plugin collects all related URLs:
- The post permalink
- The home / front page
- The post type archive
- Category and tag archive pages for the post
- The main RSS feed
For each URL, the plugin sends a GET request to the purge server:
GET https://127.0.0.1/purge/my-post-slug/
Host: yourdomain.com
Nginx matches this against location ~ /purge(/.*) {}, extracts /my-post-slug/
as $1, and purges the cache entry whose key is
httpsGETyourdomain.com/my-post-slug/.
Note: The request uses the same scheme (
https://) as the original public URL so that$schemein the cache key matches the cached entry. If your site is HTTPS but you set the purge server URL tohttp://127.0.0.1, the scheme mismatch will cause purge failures (404).
4. Purged content types
| Event | URLs purged |
|---|---|
| Post created / updated / trashed / deleted | Permalink, home, post type archive, category & tag archives, feed |
| Comment added / edited / deleted / status changed | Same as the parent post |
| Term (category / tag) edited / deleted | Term archive, home page |
5. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Purge returns 404 | Cache key mismatch — usually a scheme (http vs https) or host mismatch |
Ensure the purge server URL scheme matches how visitors access the site |
| Purge returns 403 | Nginx allow/deny blocking the request |
Add the server's loopback IP to the allow list in the /purge/ location |
| Purge returns connection refused | Wrong port or nginx not listening on that address | Verify the purge server URL and port match your nginx listen directive |
| Nothing happens on post save | Auto-purge not enabled or no server URL configured | Check both the Auto-purge checkbox and Purge Server URL are set |
CLI / Web Cache Directory
PHPFastCache can default to different temporary directories for CLI and web
contexts. This plugin avoids that problem by always setting an explicit,
fixed cache path (wp-content/uploads/pfc-object-cache/cache/).
This means WP-CLI commands, wp-cron, and web requests all share the same cache store — no stale or split caches.
Note: Ensure the CLI user and web-server user both have read/write permissions on the cache directory. The simplest approach is to run CLI commands as the web-server user:
sudo -u www-data wp cache flush.
Driver Reference
| Driver | Use Case | Persistence | Requires |
|---|---|---|---|
| Files | Shared hosting, zero infrastructure | ✅ Disk | Writable wp-content/uploads/ |
| Redis | VPS / cloud, high-traffic | ✅ Optional | Redis server + phpredis or predis |
| Memcached | High-traffic, cache-only | ❌ RAM | Memcached server + php-memcached |
| APCu | Single-server, max speed | ❌ RAM | PHP apcu extension |
| SQLite3 | Low-traffic, no Redis/Memcached | ✅ Disk | PHP pdo_sqlite extension |
File Structure
pfc-object-cache/
├── pfc-object-cache.php # Main plugin file (bootstrap)
├── composer.json
├── README.md
├── vendor/ # Composer autoloader + PHPFastCache
├── classes/
│ ├── class-cache-engine.php # PFC\ObjectCache\CacheEngine (static)
│ └── class-nginx-cache-purger.php # PFC\ObjectCache\NginxCachePurger
├── drop-in/
│ └── object-cache.php # Drop-in template (copied to wp-content/)
├── includes/
│ ├── class-pfc-drop-in-installer.php # PFC\ObjectCache\DropInInstaller
│ ├── class-pfc-cache-manager.php # PFC\ObjectCache\CacheManager
│ └── class-pfc-admin-page.php # PFC\ObjectCache\AdminPage
├── templates/
│ └── admin-page.php
└── css/
└── admin.css
Smoke Test
Add this as a temporary mu-plugin to verify the drop-in is active:
<?php
add_action( 'admin_notices', static function (): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
wp_cache_set( 'pfc_test', 'ok', 'pfc_smoke', 30 );
$found = null;
$val = wp_cache_get( 'pfc_test', 'pfc_smoke', false, $found );
echo $found && 'ok' === $val
? '<div class="notice notice-success"><p>PFC Object Cache: Active ✅</p></div>'
: '<div class="notice notice-error"><p>PFC Object Cache: Not working ❌</p></div>';
wp_cache_delete( 'pfc_test', 'pfc_smoke' );
} );
Or via WP-CLI:
wp cache type
wp cache flush
Security
- All admin actions require
manage_optionscapability - All POST forms are protected with WordPress nonces (
wp_nonce_field/check_admin_referer) - All
$_POSTinput is sanitised before use (sanitize_key,sanitize_text_field,absint) - All output is escaped (
esc_html,esc_attr,esc_url,esc_js) - File operations use the
WP_FilesystemAPI - The installer will not overwrite a drop-in belonging to another plugin
- Every PHP file guards against direct access with
defined('ABSPATH') || exit - Nginx cache purge validates the path is safe (not a system directory) and writable
declare(strict_types=1)is enabled on all PHP files to prevent type coercion bugs- All classes are wrapped with
class_exists()guards to prevent redeclaration - The
var_export-based config file only writes sanitised values and is protected by.htaccess+index.php
License
GPL-2.0-or-later — see LICENSE.