KBeezie

There's no place like ::1

Menu
  • Home
  • Start Here
  • Security Series
  • About

WordPress Plugins on a VPS: When They Hurt More Than They Help

2026/05/15

What WP-Optimize does on every single request

WP-Optimize hooks into WordPress's init action and runs loader(). This method does pre-scan housekeeping: it checks for cache directories, verifies log paths, probes for stale files, looks for its own drop-ins. It does this by calling file_exists() — repeatedly, sometimes in loops, against the filesystem.

init fires on every WordPress request. Frontend page views. Admin dashboard. AJAX calls. REST API endpoints. Anonymous bot traffic. Everything.

On a virtualized block device — Linode's virtio-backed Ceph storage — a single stat() syscall completes in microseconds under idle conditions. But dozens of file_exists() calls per request, multiplied by concurrent PHP workers, multiplied by bot traffic, creates I/O contention. The virtual disk queue backs up. Requests that normally complete in 50–200 ms suddenly block for 10+ seconds, all waiting for their turn at the filesystem.

The Googlebot crawl at 6 AM didn't cause the spike. WP-Optimize's loader() running on every bot request caused the spike. Without the plugin, those same requests would have completed in milliseconds. The 1-minute load average would have stayed at 0.5.

There were 880+ PHP requests in that two-hour window. The slowlog only captured the two that crossed the 10-second threshold, but the plugin added filesystem overhead to every single one of them. And because the server hosts multiple WordPress sites, up to 6 PHP-FPM children could be simultaneously stuck in I/O wait — each one blocking the disk queue for the others.

The bot was polite — too slow for rate limits, too valid-looking for fail2ban jails — but persistent enough that every request cost 10–50× more CPU than it should have. That's the multiplier effect. The bot was never the problem; the per-request tax was.


Why caching plugins are unnecessary on a VPS

This incident made me audit what WP-Optimize was actually doing that I couldn't do better at the server layer. The answer: nothing.

WordPress performance plugins exist because shared hosting locks you out of the server configuration. You can't tweak nginx. You can't configure PHP-FPM. You can't set up a CDN at the edge. A plugin is the only lever you have. On a VPS, that constraint disappears — and in most cases, the plugin becomes the bottleneck.

Page caching
Already handled by: a CDN edge cache (Cloudflare, Fastly, BunnyCDN, or similar)
Edge caching isn't unique to a VPS — but having a static IP and full control over the origin firewall means you can lock down access to the CDN's IP ranges, configure real-IP resolution properly, and ensure cache misses hit a server that isn't sharing resources with 300 other accounts. On shared hosting, the same CDN setup still helps, but you can't control what happens when the cache misses.
Static asset caching
Already handled by: nginx expires max, open_file_cache, sendfile
nginx serves static files in kernel space — data copies directly from filesystem cache to socket without touching userspace. open_file_cache keeps file descriptors warm, avoiding repeated stat() calls. expires max on hashed assets means return visitors never re-request the same file. A PHP plugin doing the same thing boots WordPress first, then reads the file in userspace — orders of magnitude slower, and it gets worse under concurrency.
Database cleanup
Already handled by: wp db optimize via weekly cron
Runs once per week. Zero per-request cost. Plugin equivalent runs on wp-cron or admin trigger — which means it runs during traffic.
Image compression
Already handled by: offline one-time processing, or theme built-in WebP conversion
Done once at upload, never again. A plugin re-checks on every request.
GZIP / Brotli
Already handled by: nginx gzip_static on, gzip on
Done at the edge, in C. No PHP involvement.
Minification
Already handled by: build-time or Cloudflare (if you even need it)
PHP-side minification runs regex over every page load. Never at request time.
Object caching (Redis, memcached)
Already handled by: PHP 8.5 opcache + MariaDB buffer pool
opcache holds compiled bytecode in shared memory across requests. MariaDB caches query results in its buffer pool. Adding Redis on a single-server setup adds network hops and serialization overhead for no gain.
Preloading / cache warming
Already handled by: not needed at all
The feature that caused the spike — scanning the filesystem to "warm" a cache that doesn't exist.
  • ← Previous
  • 1
  • 2
  • 3
  • Next →
©2026 KBeezie | Disclaimer | Privacy Notice