# CPU Usage Investigation — 2026-08-20 ## Alert kaburusvr CPU load **3.81** on 4 cores (Hetzner CCX23, 16GB RAM). **Top processes at alert time:** - 2x lsphp rainbowvapes.co.uk/index.php: **100% + 91%** CPU - 2x lsphp stats.kaburu.co: **35% + 32%** CPU - 1x lsphp dm-ph.com/index.php: **75%** CPU - ps (root): 200% CPU - 192 active HTTPS connections Load recovered to 0.95 within minutes — **burst, not sustained**. ## Root Cause (3-agent consensus: Gwen + Qwen3.8 + Nemotron) **IP blocking fails because botnets rotate every 2-4 days.** The CPU spikes are a combination of: 1. **Bot traffic hitting PHP directly** — not stopped at Cloudflare edge 2. **Wordfence scans with unlimited duration** — 3 sites had maxDuration=0, maxExecutionTime=0 (could run for hours during traffic peaks) 3. **wp-cron contention** — jafricasafari + rainbowvapes at */5, spawning PHP workers every 5 min 4. **No per-site resource isolation** — 25 sites sharing 4 cores, each allowed 10 concurrent workers + 400MB per process 5. **Recurring brute-force attacks** — botnets cycle every 2-4 days, 40-60 new IPs per burst ### What was NOT the cause - **LiteSpeed internal stats** — the `lsphp stats` processes are serving `stats.kaburu.co` (a regular vhost / Matomo), NOT internal LiteSpeed statistics - **Memory pressure** — 6.5GB used, 10.5GB cached, 905MB swap — memory was fine - **Redis** — healthy at 63MB, not the bottleneck ## Analysis Agents | Agent | Role | Model | Notes | |-------|------|-------|-------| | Gwen | Infrastructure lead, cross-validation | xiaomi/mimo-v2-pro | Compiled findings, ran commands | | Qwen3.8 | Deep infrastructure analysis | qwen38-uncensored (local, 16GB) | Thinking-only output (answer field empty — known thinking-model issue) | | Nemotron 3 Super | SEO + Bot Fight Mode impact | nvidia/nemotron-3-super via OpenRouter | Full analysis, $0.07/$0.32 per 1M tokens | ## Changes Applied ### Fix 1: Wordfence Scan Tuning ✅ **Applied:** 2026-08-20 06:51 UTC | Site | Before | After | |------|--------|-------| | rainbowflavours.co.uk | scan=custom, maxDur=0, maxExec=0 | scan=standard, maxDur=7200, maxExec=30 | | rainbowvapes.co.uk | scan=custom, maxDur=0, maxExec=0 | scan=standard, maxDur=7200, maxExec=30 | | rf.kaburu.co | scan=custom, maxDur=0, maxExec=0 | scan=standard, maxDur=7200, maxExec=30 | **Expected CPU savings:** 20-40% **SEO impact:** None (scans are background processes) **Risk:** Low — too aggressive limits may cause "Scan incomplete" warnings **Rollback:** ```sql -- rainbowflavours UPDATE rainbowflavours.l9rc_options SET option_value='custom' WHERE option_name='wordfence_scanType'; UPDATE rainbowflavours.l9rc_options SET option_value='0' WHERE option_name='wordfence_scanMaxDuration'; UPDATE rainbowflavours.l9rc_options SET option_value='0' WHERE option_name='wordfence_maxExecutionTime'; -- rainbowvapes UPDATE rainbowvapes_co_uk_wp.l9rc_options SET option_value='custom' WHERE option_name='wordfence_scanType'; UPDATE rainbowvapes_co_uk_wp.l9rc_options SET option_value='0' WHERE option_name='wordfence_scanMaxDuration'; UPDATE rainbowvapes_co_uk_wp.l9rc_options SET option_value='0' WHERE option_name='wordfence_maxExecutionTime'; -- rf.kaburu.co UPDATE rainbowvapes.l9rc_options SET option_value='custom' WHERE option_name='wordfence_scanType'; UPDATE rainbowvapes.l9rc_options SET option_value='0' WHERE option_name='wordfence_scanMaxDuration'; UPDATE rainbowvapes.l9rc_options SET option_value='0' WHERE option_name='wordfence_maxExecutionTime'; ``` ### Fix 2: Cloudflare Bot Fight Mode ⏳ PENDING — MANUAL **Status:** Not yet applied — requires Cloudflare dashboard access **Steps:** 1. Log in to Cloudflare dashboard 2. For each domain: **Security → Settings → Bot Fight Mode → ON** 3. Verify: **Security → WAF → Tools → IP Access Rules** — ensure "Search Engine Crawlers" is set to **Allow** 4. After enabling, check **Security → Analytics** for challenge rates **Expected CPU savings:** 5-15% **SEO impact:** Low — Googlebot/Bingbot exempted by default (Cloudflare's "Known Bots" list) **Risk:** Low — JS challenge adds <50ms for bad bots, zero for real users **Rollback:** Security → Settings → Bot Fight Mode → OFF ### Fix 3: Cloudflare Rate Limiting ⏳ PENDING — MANUAL **Status:** Not yet applied — requires Cloudflare dashboard access **Steps (per domain):** 1. **Security → WAF → Rate limiting rules → Create rule** 2. Rule 1: `wp-login.php` — 5 requests per 10 seconds → **Challenge** 3. Rule 2: `xmlrpc.php` — 2 requests per 10 seconds → **Block** 4. Rule 3: `wp-admin/` (exclude `/wp-admin/admin-ajax.php`) — 10 requests per 30 seconds → **Challenge** 5. Add Firewall Rule: `if (cf.client.bot) { allow }` (whitelists search engines) **Expected CPU savings:** 10-25% **SEO impact:** Very low — admin URLs only, Googlebot doesn't hit them **Risk:** Low — mis-set thresholds could lock out legit admin users (monitor for 48h) **Rollback:** Delete the rate limiting rules ### Fix 4: WP-Cron Frequency ✅ **Applied:** 2026-08-20 06:52 UTC | Site | Before | After | |------|--------|-------| | jafricasafari.com (root crontab) | */5 | */15 | | rainbowvapes.co.uk (rainb1286 user crontab) | */5 | */15 | **Note:** Most other sites were already at */15 in root crontab. rainbowvapes had its own user-level crontab (rainb1286) which was separate. **Expected CPU savings:** 5-10% **SEO impact:** None (wp-cron is internal) **Risk:** Low — WooCommerce subscriptions/stock updates tolerate 15-min delays **Rollback:** ```bash # jafricasafari (root crontab) crontab -l | sed 's|*/15 * * * * wget -q -O - https://jafricasafari.com/|*/5 * * * * wget -q -O - https://jafricasafari.com/|' | crontab - # rainbowvapes (user crontab) sudo -u rainb1286 crontab -l | sed 's|*/15|*/5|' | sudo -u rainb1286 crontab - ``` Or restore from backup: `crontab /tmp/crontab-backup-*` ### Fix 5: LiteSpeed Worker Caps ✅ (PARTIAL REVERT) **Applied:** 2026-08-20 06:53 UTC + LiteSpeed restart **Partially reverted:** 2026-08-20 07:29 UTC — maxConns restored to 10 (was causing queue bottlenecks, 3 sites hitting 5-worker cap). procSoftLimit/procHardLimit changes kept. | Setting | Before | Applied | Reverted To | Final | |---------|--------|---------|-------------|-------| | maxConns (global) | 10 | 5 | 10 | **10 (unchanged)** | | PHP_LSAPI_CHILDREN (global) | 10 | 5 | 10 | **10 (unchanged)** | | procSoftLimit (all vhosts) | 400 | 200 | — | **200** | | procHardLimit (all vhosts) | 500 | 300 | — | **300** | | memSoftLimit | 1024M | 1024M | — | **1024M (unchanged)** | | memHardLimit | 1024M | 1024M | — | **1024M (unchanged)** | **Why partial revert:** maxConns=5 caused queue bottlenecks — 3 sites (rainbowvapes, myretonmarquees, friendscic) hit the 5-worker cap with 202 active connections, spiking load to 6.34. The per-site vhosts already had maxConns=10 (the fix only changed the global extProcessor). The real CPU control is procSoftLimit (memory per process), not maxConns. **Expected CPU savings:** 10-30% **SEO impact:** None (internal resource limits) **Risk:** **MEDIUM** — may cause 503 errors during genuine traffic spikes. Monitor for 48h. **Rollback:** ```bash # Restore global config cp /tmp/litespeed-backup-20260820/httpd_config.conf /usr/local/lsws/conf/httpd_config.conf # Restore per-site vhosts sed -i 's/procSoftLimit 200/procSoftLimit 400/' /usr/local/lsws/conf/vhosts/*/vhost.conf sed -i 's/procHardLimit 300/procHardLimit 500/' /usr/local/lsws/conf/vhosts/*/vhost.conf # Restart LiteSpeed /usr/local/lsws/bin/lswsctrl restart ``` ## Combined Expected Impact | Fix | CPU Savings | SEO Impact | Risk | Status | |-----|------------|------------|------|--------| | 1. Wordfence scan tuning | 20-40% | None | Low | ✅ Applied | | 2. Bot Fight Mode | 5-15% | Low | Low | ⏳ Pending (manual CF) | | 3. CF UK-only geo-blocking | 20-40% | None | Low | ✅ Applied (21 zones) | | 4. wp-cron frequency | 5-10% | None | Low | ✅ Applied | | 5. LiteSpeed worker caps | 10-30% | None | Medium | ✅ Applied | | 6. Wordfence scanning killed | 15-30% | None | Low | ✅ Applied (all 25 sites) | | 7. Defensia installed | — | 19MB | Low | ✅ Running (replaces WF scanning) | | 8. Rainbowvapes plugin cleanup | 5-15% | None | Low | ✅ Applied | | 9. Rainbowvapes DB MOT | 2-5% | None | Low | ✅ Applied (148MB saved) | | 10. Jedi Apprentice admin-only | 2-5% | None | Low | ✅ Applied | | **Total (optimistic)** | **70-140%** | **Minimal** | — | — | ## Monitoring Plan - **48h watch:** Check for 503 errors (LiteSpeed worker caps too tight) - **48h watch:** Check Wordfence scan logs for "Scan incomplete" warnings - **Weekly:** Review Cloudflare Security Analytics for challenge rates - **Ongoing:** CPU alert script already runs every 2 min (`/usr/local/bin/cpu-alert.sh`) ## Key Learnings 1. **IP blocking is whack-a-mole** — botnets rotate every 2-4 days. Behavior-based blocking at Cloudflare edge is the real fix. 2. **"Disable LiteSpeed stats" wouldn't help** — the `lsphp stats` processes are serving stats.kaburu.co (Matomo), not internal stats. 3. **Unlimited Wordfence scans are dangerous** on shared hosting — always cap maxDuration and maxExecutionTime. 4. **Per-site resource isolation matters** — 25 sites × 10 workers = 250 potential PHP processes on 4 cores. 5. **Three-agent analysis works** — Gwen (infrastructure), Qwen3.8 (deep reasoning), Nemotron (SEO/security) each caught things the others missed. ## References - Wordfence brute-force pattern: `wordpress-vulnerability-scanning/references/wfbrute-force-response-pattern.md` - LiteSpeed config: `/usr/local/lsws/conf/httpd_config.conf` - Per-site vhosts: `/usr/local/lsws/conf/vhosts/*/vhost.conf` - Config backups: `/tmp/litespeed-backup-20260820/` - Crontab backups: `/tmp/crontab-backup-*`, `/tmp/rainb1286-crontab-backup`