wp-login.php show only an occasional request. Security plugins configured to block IP addresses after five failed login attempts remain quiet. If you look only at the login URI, everything looks peaceful. Looking at xmlrpc.php, however, reveals a steady drip of HTTP POST requests that look harmless on the surface but are consuming PHP-FPM workers and database connections behind the scenes.
This pattern usually points to an XML-RPC brute-force attack leveraging the system.multicall method. When troubleshooting high load on a WordPress hosting environment, this is one of the easiest attack vectors to misdiagnose because it intentionally works around traditional per-request rate limiting.
What XML-RPC multicall actually does
xmlrpc.php to allow external applications—like mobile publishing apps, remote editors, and desktop clients—to interact with the site over HTTP using XML data formats. Inside the XML-RPC specification is a utility method called system.multicall. Its intended purpose is efficiency: instead of establishing separate HTTP connections to perform ten different administrative actions, an authorized client can bundle ten commands into a single XML envelope and send them in one POST request.
The complication arises because XML-RPC methods like wp.getUsersBlogs or metaWeblog.getUsersBlogs validate authentication on every call using a plaintext username and password inside the payload. When an attacker wraps multiple authentication calls inside a system.multicall wrapper, WordPress processes each nested method call sequentially during a single execution cycle of xmlrpc.php.
If an attacker packs five hundred credential combinations into a single POST body, the web server sees exactly one HTTP request. Standard firewalls and rate limiters monitoring request frequency per IP address see a low-volume client making a request every few seconds. Meanwhile, the PHP backend runs five hundred database queries against the wp_users and wp_usermeta tables, hashing passwords and validating users within a single PHP process. If several bots do this at the same time, PHP-FPM pools saturate quickly.
Why access logs disguise the attack
/wp-login.php, every single attempt creates one line in the access log, almost always returning an HTTP 200 (if rendering the login page with an error) or an HTTP 302 redirect. A quick inspection using wc -l or grep reveals thousands of lines from a single IP address.
With system.multicall, the logs tell a deceptive story. The request line will show an HTTP POST to /xmlrpc.php returning an HTTP 200 status code. Because XML-RPC returns an HTTP 200 status even when individual sub-methods return authentication failures (which are packaged inside XML <fault> responses), the HTTP status code alone does not reveal an attack. As discussed in our look at why HTTP 200 in access logs does not mean an exploit succeeded, status codes describe transport success, not business logic outcomes.
To identify a multicall attack, you have to look at the metrics that sit beside the status code: the byte size of the response, the frequency of POST requests relative to legitimate site activity, and, if your logs record it, the request processing time.
Spotting the fingerprint in raw logs
system.multicall brute-force attack generates distinct log signatures. Because the server returns an XML array containing hundreds of failure responses within that single HTTP response, the byte count sent back to the client is consistently much larger than a standard pingback.
You can inspect these patterns using basic command-line tools against your web server's access log file:
BASH
# Filter POST requests to xmlrpc.php and print IP, response code, and bytes sent
awk '6 ~ /POST/ &&7 ~ /xmlrpc\.php/ {print 1,9, $10}' /var/log/nginx/access.log | head -n 301 is the remote IP address, 9 is the HTTP response code, and $10 is the body bytes sent. An example format output during a multicall scan often looks like this:
TEXT
192.0.2.45 200 48210
192.0.2.45 200 48195
192.0.2.45 200 48220
198.51.100.12 200 32150
198.51.100.12 200 32144Safe verification on a test VPS
curl. You do not need automated brute-force tools to see the mechanism in action; sending a small XML payload containing a single system.multicall with invalid credentials illustrates the log and payload behavior immediately.
Here is how to structure a diagnostic call against a local test instance:
BASH
curl -X POST -H 'Content-Type: text/xml' \
-d '<?xml version="1.0"?><methodCall><methodName>system.multicall</methodName><params><param><value><array><data><value><struct><member><name>methodName</name><value><string>wp.getUsersBlogs</string></value></member><member><name>params</name><value><array><data><value><string>testuser</string></value><value><string>testpass</string></value></data></array></value></member></struct></value></data></array></value></param></params></methodCall>' \
http://localhost/xmlrpc.phpwp.getUsersBlogs, fail authentication, and wrap that error in a standard XML-RPC response structure containing fault code 403 (or similar, depending on the WordPress version). In your server's access log, you will see a POST /xmlrpc.php HTTP/1.1 entry with a 200 status code.
If you duplicate that <value><struct>...</struct></value> block fifty times inside the <data> array, the execution time will visibly increase, the returned XML will repeat fifty fault blocks, and the logged bytes sent will climb proportionally.
What to check before changing configurations
xmlrpc.php at the web server level is indeed the cleanest fix, especially if the site relies purely on the modern WordPress REST API.
However, there are environments where legacy systems or specific third-party services require access to xmlrpc.php. For example, older publishing integrations or certain features of management plugins route their monitoring checks through this file. If you block the file completely using an Nginx location = /xmlrpc.php { deny all; } directive or an Apache .htaccess rule, verify whether the site owner uses external mobile publishing or automated syndication that relies on legacy endpoints.
If xmlrpc.php must remain accessible for specific remote systems, the safer path is whitelisting trusted source IPs or configuring the web server to block requests specifically when the request method is POST and the payload contains system.multicall. For sites that do not use any mobile apps or external XML-RPC publishing tools, terminating all access to xmlrpc.php directly at the web server layer relieves PHP and MySQL from parsing these payloads entirely.
Recognizing that low request volumes can hide thousands of authentication attempts helps support teams locate the real cause of sudden load spikes when standard login logs appear completely clean.