xmlrpc.php. Because each incoming request triggers a full PHP bootstrap and multiple database queries, a modest flood of automated requests can saturate every available PHP-FPM worker in seconds.
At the hosting support desk, this pattern often leads to confusion. Server owners notice load alerts climbing from nominal levels to high double digits, assume they are undergoing a distributed denial-of-service attack, and immediately look into upgrading their VPS plan. Scaling the server rarely fixes the underlying issue. The botnet simply scales its request rate to fill whatever capacity you add. To keep the server stable, the requests must be throttled or filtered long before they ever reach the PHP runtime.
Why xmlrpc.php Consumes Disproportionate Server Resources
xmlrpc.php remains enabled by default across standard installations for backwards compatibility.
Automated credential-stuffing tools favor this endpoint because of an XML-RPC feature called system.multicall. In a normal login attempt via wp-login.php, an attacker sends one set of credentials per HTTP POST request. With system.multicall, an attacker can bundle hundreds of authentication attempts into a single HTTP POST request. When the web server hands that request to PHP-FPM, PHP must parse the XML payload, initialize the WordPress database layer, and loop through hundreds of password-hashing evaluations (typically using bcrypt or phpass) within a single execution cycle.
Password hashing algorithms are intentionally compute-intensive to prevent brute-force attacks. When ten or twenty concurrent bots send multicall requests simultaneously, the server CPU hits 100% almost immediately. Each PHP-FPM child process remains occupied for hundreds of milliseconds or even seconds while churning through the password checks. Once the pool reaches its pm.max_children ceiling, any new incoming request—including those from regular visitors browsing your homepage—gets queued up until it hits a timeout. This is often the quiet culprit behind sudden gateway errors, as detailed in our guide on reading Nginx and PHP-FPM logs to trace 502 errors.
The Misconception About Outright Blocking
xmlrpc.php completely. Dropping a deny all; directive in Nginx or an Apache .htaccess rule that returns a 403 Forbidden code certainly stops PHP-FPM exhaustion. If your site is completely self-contained and you manage everything through the standard wp-admin dashboard, outright blocking is an effective and zero-overhead solution.
However, a complete block is not universally applicable, which is where many site administrators run into trouble. Several widely used integrations still rely on XML-RPC communication. The most prominent example is Automattic's Jetpack plugin, which uses the endpoint to sync site data, monitor uptime, and publish posts remotely from WordPress.com. Similarly, third-party syndication platforms, automated social media cross-posters, and certain native mobile publishing apps depend on XML-RPC calls.
If you block the endpoint entirely on a site that depends on these tools, administrative workflows break silently. Jetpack drops its connection, mobile drafts fail to publish, and external webhooks begin returning client errors. This leaves system administrators in a dilemma: leave the door wide open and suffer periodic CPU spikes, or lock it completely and break necessary services. Rate limiting provides the balance by allowing legitimate, occasional API calls to pass through while choking the high-frequency bursts generated by automated scanners.
Throttling XML-RPC at the Nginx Layer
ngx_http_limit_req_module. The module uses the leaky bucket algorithm to enforce average request rates while permitting controlled bursts. We define a shared memory zone in the http block of the Nginx configuration, track requests by the client's IP address ($binary_remote_addr), and apply the restriction specifically to requests targeting xmlrpc.php.
NGINX
# Place inside the http {} block of nginx.conf
limit_req_zone $binary_remote_addr zone=xmlrpc_limit:10m rate=2r/s;
# Place inside the server {} block for the WordPress site
location = /xmlrpc.php {
limit_req zone=xmlrpc_limit burst=5 nodelay;
limit_req_status 429;
# Forward legitimate traffic to the existing PHP-FPM fastcgi block
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME document_rootfastcgi_script_name;
}xmlrpc_limit allocates 10 megabytes of RAM to store tracking states for client IP addresses (10 megabytes can hold roughly 160,000 distinct IP addresses). The rule permits a baseline of 2 requests per second. The burst=5 parameter allows a brief spike of up to five requests to accommodate batch synchronization from services like Jetpack. The nodelay directive tells Nginx to process legitimate burst requests immediately without introducing artificial latency, but to drop any request exceeding the burst limit instantly with an HTTP 429 (Too Many Requests) response.
Because Nginx rejects the excess traffic before invoking FastCGI, an attacker sending twenty requests per second will see eighteen of them dropped with negligible CPU impact on the host.
Safely Verifying Throttling on a Staging Environment
curl:
BASH
# Send 10 rapid POST requests to observe the HTTP status code response
for i in {1..10}; do
curl -s -o /dev/null -w '%{http_code}
' -X POST https://example.com/xmlrpc.php
done429 status codes for the remainder of the burst.
Next, examine the Nginx error log (typically located at /var/log/nginx/error.log). When a request is throttled by the leaky bucket algorithm, Nginx writes an entry indicating that the limiting zone was exceeded, noting the client IP address and the specific request path. If your error log shows these entries while your PHP-FPM status page (pm.status) shows active worker counts remaining calm and stable, the filter is doing its job.
