We have written about individual WordPress denial-of-service vectors before — why "behind Cloudflare" is not the same as safe, the 167-byte request that exhausts a worker pool, and the search box nobody rate-limits. Each was one door. This is the whole building.

Step back from the individual vectors and a single shape appears. The dangerous parts of a WordPress deployment are not its vulnerabilities in the classic sense — they are its normal, intended features. The REST API is supposed to answer. Search is supposed to run. XML-RPC is supposed to accept a call. None of that is a bug. The attack surface is the set of endpoints where an unauthenticated stranger can make your origin do disproportionate work — and on a default install, that set is larger and more connected than most operators realise. This is the map.

The one rule that defines the surface

An endpoint belongs on the dangerous list if it satisfies four conditions at once:

  • It is reachable unauthenticated — no login required to call it.
  • It bypasses the CDN cache — so every call reaches your origin, not an edge node.
  • It triggers expensive work — a framework bootstrap, a database scan, an outbound call.
  • It has no default rate limit — nothing counts how fast it is being called.

Most of a WordPress site fails that test and is therefore safe: cached pages are served from the edge and never wake PHP. The surface is the handful of routes that pass all four. Map those, defend those, and you have closed the vast majority of the practical risk.

Availability is not a subset of security you can bolt on later. For a content business, an origin that can be pushed to HTTP 503 on demand is a security failure with a marketing-department blast radius — as the next section shows.

Vector 1 — the REST API forwards straight to PHP

The /wp-json/ API's write and dynamic routes are POSTs — or authenticated, nonce-carrying requests — and a CDN cannot cache either, so it forwards every one of those calls straight to the origin. A form-feedback route or another write endpoint then boots the framework, validates, sometimes makes an outbound call, and occupies one PHP-FPM worker for the full 1.2–3 seconds it takes. A standard small origin runs perhaps 15–25 workers. The arithmetic is unforgiving: a few dozen concurrent calls saturate the pool and every visitor — homepage, shop, checkout, not just the API caller — starts getting timeouts.

We took this apart in full in the Cloudflare myth and 167 bytes. The one-line summary: the CDN protects what it can cache, and it cannot cache a POST. The fix lives at the edge, not in a plugin — a rate-limit rule on the dynamic path so the flood never reaches a worker. On authorized test infrastructure, sustained concurrent load against a single unauthenticated REST route was enough to roughly double baseline response time and hold the worker pool under continuous pressure — with the CDN reporting nothing unusual, because from its side it was simply forwarding ordinary POSTs.

Vector 2 — an availability failure becomes a ranking failure

This is the vector operators underestimate most, because the damage outlives the attack. When your origin returns HTTP 503 under load, human visitors get an error and try again later. Googlebot treats it as a signal: a 503 means "busy, come back later," and Google backs off its crawl rate. Sustained or repeated 503s across a crawl window are read as a site that cannot reliably serve — and an unreliable site is a worse search result. Crawl frequency drops, freshly published pages are indexed slowly or not at all, and in a prolonged episode existing pages can lose position.

The chain is: worker exhaustion → 503 to everyone including Googlebot → reduced crawl and trust → ranking decline. The attacker never touched your content, your database, or your rankings directly. They touched your worker pool, and the search engine did the rest. Recovery is not instant either — crawl trust returns on the crawler's schedule, not yours.

The defense is the same edge rate-limiting that protects availability in the first place: if the origin never returns 503, the deranking chain never starts. This is precisely why we treat uptime under adversarial load as part of global reliability and platform integrity rather than as an ops afterthought — the reputational cost of being seen as down is larger than the minutes of downtime.

Vector 3 — an open origin port makes the CDN decorative

Every protection a CDN offers — the WAF, bot management, rate limiting, the JS challenge — assumes traffic actually flows through the CDN. That assumption breaks the moment an attacker can reach your origin server directly. If ports 80, 443, or 8080 are open to the world on the origin's own IP, an attacker who learns that IP simply points their traffic at it and every edge rule becomes decorative.

And origin IPs are not secret. Historical DNS records can still carry an old A record that points straight at the origin, and public Certificate Transparency logs expose the hostnames on your certificates — some of which resolve past the CDN to the origin. Treat your origin IP as discoverable, not hidden. The defense is not obscurity; it is a firewall. Two measures close this:

  • Restrict the origin to your CDN. Allow inbound 80/443 only from your CDN provider's published address ranges; drop everything else. Now a direct-to-origin request never completes.
  • Serve a guard on any non-CDN request. Requests that arrive with the wrong Host (i.e. straight to the IP, bypassing the edge) should get a bare, static rejection — not your real application — so a direct hit is cheap to refuse and reveals nothing.

We run exactly this posture on our own infrastructure: the origin refuses direct-IP traffic, and non-CDN requests get a static guard page rather than the app. It is a fifteen-minute change with an outsized payoff — it turns the CDN from a suggestion into a requirement.

Vector 4 — XML-RPC, the endpoint still enabled by default

Years after the REST API superseded it, xmlrpc.php still ships enabled on a default WordPress install. It matters here for one structural reason: the system.multicall method lets a single HTTP request carry many individual method calls batched together. Brute-force-protection plugins overwhelmingly count HTTP requests — so one request that batches a large number of credential checks registers as a single event and sails under a per-request rate limit that a login-page attack would trip immediately. It is the same request-counting blind spot that lets batched work hide.

Unless you have a specific, current dependency on it (some legacy mobile-publishing and pingback workflows), the correct posture is to turn it off. Block it at the edge, and again at the web server so there is no gap:

# At the CDN edge
(http.request.uri.path eq "/xmlrpc.php")  ->  Block

# At the web server (Apache)
<Files xmlrpc.php>
    Require all denied
</Files>

An endpoint you have blocked is one you never have to rate-limit, monitor, or reason about again.

What is not the threat anymore — an honest negative result

Threat models decay if you never remove anything from them. Slowloris — the classic slow-HTTP attack that holds sockets open by dribbling out partial headers — is a good example of a vector that has aged out against a modern stack. In our testing against a current edge-plus-application-server configuration with connection-level protections in place, every slow-socket attempt failed to establish; the sockets were dropped before they could tie anything up. Slowloris is effectively non-viable there, and time spent worrying about it is time not spent on the vectors above that do work.

The lesson is not "Slowloris is dead everywhere" — it is that the threat moved up the stack, from the connection layer to the application layer. The expensive, uncacheable, unauthenticated endpoint is where the modern pressure lands. Defend the layer that is actually under attack.

The surface on one page

Consolidated, the map looks like this — the same four-part test applied to every notable route, with the highest-leverage fix for each:

EndpointCDN statusRiskHighest-leverage fix
Homepage, posts, pagesCACHEDNone
/wp-json/ dynamic routesBYPASS (POST)CriticalEdge rate-limit the POST path
/?s= searchBypassHighRedirect if unused, else edge rate-limit
Cart / checkoutBypassHighRate-limit; keep origin headroom
/wp-login.phpBypassMediumEdge rate-limit POST
/xmlrpc.phpBypassMediumBlock outright unless required
Origin IP, ports 80/443N/ACriticalFirewall to CDN ranges only

The full, layered configuration — edge rules, WordPress settings, PHP-FPM and OPcache tuning, and what to monitor — is written out step by step in the complete hardening guide. The deeper architectural argument, that security belongs in the server itself rather than in a stack of after-the-fact configuration, is in the next web server.

Fifteen minutes to close most of it

  • Rate-limit the REST POST path at the edge — the single highest-value rule.
  • Redirect or rate-limit search — it is core, on by default, and heavier than the contact form.
  • Block xmlrpc.php — most sites have no live use for it.
  • Firewall the origin to your CDN's ranges — so the edge cannot be bypassed.

None of these require new software, and together they retire the majority of the practical attack surface. The endpoints are normal; the exposure comes from letting normal endpoints be called abnormally fast from anywhere. If you would like a second pair of eyes mapping your own surface the way we mapped this one, that is what our cyber defense practice does — and it is the same discipline behind the platforms we build to run under real-world pressure.

This is defensive security research. Every technique here is described to help operators find and close their own exposure; every measurement was produced on authorized test infrastructure and submitted for responsible disclosure. Only test systems you own or have explicit written permission to test.