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

At 06:08 on May 15, Monit fired a Discord alert:

Service Failure: kbeezie.com
loadavg (1min) of 4.7 matches resource limit [loadavg (1min) > 2.0]

On a single-core 2 GB Linode, a load average of 4.86 means nearly five processes queued waiting for CPU. Memory had doubled — from a baseline of ~620 MB to 1,285 MB. Within 30 minutes, the server recovered on its own. Load dropped back to 0.02. Memory settled at 809 MB. Nothing had crashed. No OOM kill. Whatever it was, it passed.

The question was what caused it.


Ruling out the obvious

Bots? The access log showed 1,719 requests between 05:00 and 07:00. The busiest IP — a Google crawler — made 967 requests over two hours: author enumeration, REST API user probing, wlwmanifest.xml lookups. That's ~8 req/min. Nginx's limit_req zone (5 req/s, burst 10) never triggered. The nginx-ratelimit fail2ban jail never banned it. 8 req/min is indistinguishable from an engaged human reader browsing a blog.

fail2ban API storm? 14 bans in the same window. 14 synchronous AbuseIPDB curl calls. Not enough to move the needle.

wp-cron? Already disabled on every site (define('DISABLE_WP_CRON', true)). System cron hits /wp-cron.php every 10 minutes via curl. No cron storms possible.

Backups? Blocklist updates? The 2 AM abuse blacklist update completed in seconds. The 3 AM Squid blocklist update barely registered. Certbot wasn't scheduled until 06:45.

None of the usual suspects were responsible. Something was making PHP requests expensive in a way they shouldn't have been.


The break: PHP-FPM slowlog

I'd enabled PHP-FPM's slowlog earlier in the week — request_slowlog_timeout = 10s. Two entries appeared during the spike window:

05:58:57 — pid 3547081

script_filename = /home/user/www/example-site.com/public_html/index.php
[0x00007...] file_exists() /wp-content/plugins/wp-optimize/wp-optimize.php:236
[0x00007...] loader() /wp-content/themes/Avada/includes/lib/inc/class-fusion.php:246
→ 404.php template render

06:10:11 — pid 3547164

script_filename = /home/user/www/example-site.com/public_html/index.php
[0x00007...] file_exists() /wp-content/plugins/wp-optimize/wp-optimize.php:236
[0x00007...] loader() /wp-content/themes/Avada/includes/lib/inc/class-fusion.php:246
→ avada_main_menu() header render

Two different PHP-FPM children. Two different request paths — one a 404 page, one a normal page view. Identical blocking point: wp-optimize.php:236, a file_exists() call inside WP-Optimize's loader() method.

  • ← Previous
  • 1
  • 2
  • 3
  • Next →
©2026 KBeezie | Disclaimer | Privacy Notice