Sultan Kautsar Get a quote

When Scanners Overwhelmed WordPress

On the evening of October 25, 2026, automated scanning traffic overwhelmed a server hosting several WordPress sites for one client. Unknown URLs triggered expensive PHP processing, and thousands of queued requests kept the server overloaded after the initial traffic burst had passed.

Recovery took three steps: clearing the backlog, limiting the work admitted to PHP, and rejecting nonexistent content files before they reached WordPress. The last step mattered because protecting the server's capacity did not immediately stop scanners from disrupting one site's visitors.

3 min read

The Overload

The scanner requested thousands of different paths across several websites. On the investor site alone, logs recorded 9,484 requests identifying themselves with the ffuf user-agent. The Australia site received another 7,277.

Many of those URLs did not exist. The sites’ routing still passed them to WordPress, so PHP and WordPress could run just to return an error. A missing file was cheap to request and expensive to reject.

At 7:58 p.m. WIB, the four-core server had a load average of 27.91 and no idle CPU capacity. Visitors encountered timeouts and failed requests.

The incoming traffic was only part of the problem. At approximately 8:07 p.m., four affected PHP pools held a combined 6,673 queued connections. Even after the initial burst passed, the server still had that accumulated work waiting to run. Looking only at new requests would have missed why it remained overloaded.

The evidence supported scanner-driven resource exhaustion. It did not establish a compromise or identify a particular WordPress plugin as the cause.

Clearing Queues

Restarting PHP 8.2 cleared the accumulated queues. By 8:11 p.m., the affected queues were empty, CPU idle capacity had returned to 54–61%, and the server load was falling.

That restored capacity, but it did not change what the next burst could ask the server to do. The next step was to bound the work that could reach PHP and the time it could occupy a worker.

Capacity Limits

Protections were deployed across fourteen applications:

  • PHP request limits per website and across the server.
  • Smaller PHP connection queues, with capacity reduced to 32 per pool.
  • Request termination deadlines.
  • Early rejection of the observed ffuf user-agent.

The user-agent block addressed the observed traffic. The general limits continued to apply even if scanners changed their user-agent. Static assets were excluded from PHP admission limits.

By approximately 8:49 p.m., all fourteen canonical homepages returned HTTP 200 in origin checks, and all fourteen PHP queues were empty.

Rejecting Missing Files

Later that evening, further scanning targeted the career website. The new limits helped preserve overall server capacity, but scanner requests still consumed that site’s PHP allowance. Some normal homepage requests and recurring REST checks consequently received HTTP 429 responses.

The limits were doing their job at the server level, while visitors still competed with scanners for the same site’s allowance. Reducing the cost of the scanner requests required moving their rejection ahead of PHP admission.

The targeted change rejected nonexistent paths under /wp-content/ in NGINX. Existing files and directories retained their handlers, and sensitive upload and private-log paths retained their protections. The WordPress hardening note covers the separate NGINX rule that blocks PHP execution from writable content folders. The change was activated at approximately 10:31 p.m. WIB.

A nonexistent content-file request that previously took 0.571 seconds returned in 0.016 seconds afterward. In a bounded verification test, all 24 nonexistent content requests returned 404, while simultaneous homepage and REST requests returned 200.

Those checks showed that the tested missing content paths could be rejected quickly without consuming the PHP allowance needed by the simultaneous legitimate requests.

Lessons

Returning a 404 can still be expensive when WordPress has to generate it. The response code alone does not tell you how much work the server performed.

The recovery addressed three separate parts of the overload:

  • Clear the backlog. Stopping the incoming burst does not remove thousands of requests already waiting for PHP.
  • Bound the work. Admission limits, smaller queues, and deadlines reduce how much capacity a burst can occupy and how long its effects can persist.
  • Reject missing content early. For the targeted /wp-content/ paths, NGINX could return the error before scanners consumed the career site’s PHP allowance.

This follows the same capacity problem I described in When the Server Fell Over: traffic to one site can affect every site sharing its server. This incident also showed why server recovery and visitor recovery need separate checks. Empty queues and healthy CPU capacity did not, on their own, mean legitimate requests had stopped receiving 429 responses.

The recorded checks confirmed recovery and the targeted mitigation. Continued monitoring remains necessary for legitimate traffic receiving 429 responses and scanner patterns outside the protected paths.

Based on the investigation and deployment notes from October 25, 2026. The checks covered key endpoints and bounded probes, rather than a complete application test or sustained-load benchmark. All times are WIB (UTC+7).

Related Notes

  1. When the Server Fell Over

    How a Facebook crawler flood took every WordPress site on a shared server down, why a full opcache looked like an attack, and the caps, crawler caching, and checks that keep one site from sinking the rest.

  2. Locking and Watching WordPress

    How I made the code of about 50 WordPress sites immutable with chattr, what that broke, the two protections that had silently never run, and a daily Telegram report built to have almost no false alarms.

Next step

Have a project in mind? Let's talk.

Tell me what you have and what you need. I'll usually reply within one working day with how I'd approach it.