# 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`
