XML-RPC in WordPress – Risks and Safe Handling

XML-RPC in WordPress – Risks and Safe Handling

XML-RPC in WordPress is one of those features most site owners never use but nearly all of them still expose. If you run a WordPress site, xmlrpc.php is probably still reachable, and it gives attackers a quieter path for password guessing, DDoS reflection and internal network probing. This article covers what XML-RPC actually does in WordPress, which risks are real and which are overstated, and how to disable or restrict it without breaking integrations you depend on.

What XML-RPC Does in a Modern WordPress Install

XML-RPC is a remote procedure call protocol that wraps commands in XML and sends them over HTTP POST. WordPress has turned it on by default since version 3.5, released in December 2012. Before that, it was an opt-in setting under Writing options.

It was built for a world without the REST API. Desktop blogging clients, early mobile apps and pingback notifications between blogs all depended on it. The WordPress REST API reached core in version 4.7 (late 2016), and Application Passwords arrived in 5.6 (December 2020). Together they cover nearly every legitimate use case XML-RPC used to serve.

The file still ships in every release, though. Nothing calls it on most sites, yet it accepts requests from anyone on the internet.

Real Attack Patterns Against xmlrpc.php

The most common abuse is amplified brute force through system.multicall. A single HTTP request can bundle hundreds of wp.getUsersBlogs calls, each with a different password. One POST becomes 500 login attempts. Rate limiting that counts requests per minute barely notices. For broader context on detecting these campaigns, see this guide on brute force detection and prevention strategies.

The second pattern is pingback reflection. The pingback.ping method makes your server fetch a URL supplied by the caller. In 2014, Sucuri documented a campaign where more than 162,000 legitimate WordPress sites were used to flood a single target with pingback-driven requests. The site owners had done nothing wrong. Their servers were simply doing what pingback was designed to do.

The same mechanism enables server-side request forgery. An attacker can point pingback at internal addresses such as 127.0.0.1:6379 or a cloud metadata endpoint and infer which ports are open from response timing or error codes. Full data exfiltration through pingback is rare. Port mapping and service discovery are not. The broader mechanics are covered in this explanation of SSRF attacks and their risks.

A typical scenario looks like this. A WooCommerce shop on a $20/month VPS starts showing CPU at 90-100% every evening for about 40 minutes. The security plugin’s lockout log is empty, because it only watches wp-login.php. The Nginx access log tells the real story: roughly 12,000 POST requests to /xmlrpc.php in one hour, spread across 300+ IP addresses, each returning HTTP 200. Every one of those requests was a multicall carrying dozens of password guesses.

The Myth That One Filter Turns XML-RPC Off

Countless tutorials recommend adding add_filter(‘xmlrpc_enabled’, ‘__return_false’); and treating the job as done. That filter does less than its name suggests.

Since WordPress 3.5, xmlrpc_enabled only disables methods that require authentication. Pingback methods do not authenticate, so they keep working. xmlrpc.php still loads WordPress, parses the XML body and responds. The brute-force vector gets closed, but the reflection and SSRF vectors stay open, and every request still costs a full PHP bootstrap.

An experienced WordPress auditor checks the actual behaviour instead of trusting the snippet. Sending a system.listMethods call with curl shows what is really exposed:

curl -s -d ‘<methodCall><methodName>system.listMethods</methodName></methodCall>’ https://example.com/xmlrpc.php

If pingback.ping appears in the response, the site is still exploitable for reflection, whatever the plugin settings page claims.

Safe Handling of WordPress XML-RPC Step by Step

The right approach depends on whether anything legitimately needs the endpoint. A practitioner works through it in this order:

1. Inventory dependencies. Check for Jetpack, the official WordPress mobile app using legacy login, IFTTT or Zapier legacy connectors, and desktop editors like Open Live Writer. Search the access logs for POSTs to xmlrpc.php from known service IPs over the past 30 days.
2. Block at the web server if nothing depends on it. In Nginx, use location = /xmlrpc.php { deny all; }. In Apache 2.4, wrap Require all denied inside a <Files xmlrpc.php> block. This stops requests before PHP loads, which is what actually saves CPU.
3. Allowlist instead of blocking if Jetpack is in use. Jetpack’s connection relies on xmlrpc.php. Automattic publishes the IP ranges Jetpack uses, and you can allow only those while denying everyone else.
4. Strip pingback methods if partial access is required. Use the xmlrpc_methods filter to unset pingback.ping and pingback.extensions.getPingbacks, and remove the X-Pingback header through the wp_headers filter.
5. Verify externally. Re-run the curl test from outside your network. You should get a 403 or 404 rather than an XML response.

After that, add xmlrpc.php to your regular security audit. Plugin updates, server migrations and a new caching layer have all been known to quietly bring the endpoint back. The rest of the admin-side hardening is covered in these 10 steps to secure the WordPress admin panel.

Mistakes That Leave the Endpoint Exposed

Blocking at the origin while the CDN serves a cached path. Teams add a deny rule on the origin but leave the Cloudflare or other CDN rules unchanged. That’s usually fine, until someone finds the origin IP through historical DNS records and sends requests straight to it. Restrict the origin to accept traffic only from your CDN’s IP ranges.

Relying on a login-lockout plugin that ignores XML-RPC. Some lockout plugins hook only wp-login.php. Others count a multicall request as one failed attempt, not 500. Check how your plugin handles authentication through XML-RPC before you trust its lockout log.

Disabling XML-RPC and then breaking Jetpack without noticing. Jetpack Protect, Stats and backups lose their connection silently, and the site owner discovers it weeks later when a restore is needed. Test every integration from your inventory after making the change.

When Blocking Is Not the Right Answer

Some environments legitimately need XML-RPC. Newsrooms running legacy editorial tools, multisite networks with older syndication setups, and agencies managing hundreds of client sites through remote management platforms may all rely on it. In those cases, restrict access by IP, require Application Passwords rather than main account credentials, and enforce strong passwords on every account with publishing rights.

Sites behind an internal VPN or on an intranet-only deployment face lower exposure from the internet. The SSRF angle matters more there, because pingback can reach internal services an attacker couldn’t otherwise touch.

FAQ

Does disabling XML-RPC break the WordPress mobile app?
Current versions of the WordPress mobile app connect to self-hosted sites through the REST API and Application Passwords. Very old app versions and some sites connected through Jetpack may still depend on XML-RPC, so test after making the change.

Is it safe to delete xmlrpc.php from the server?
Deletion works temporarily, but every WordPress core update restores the file. A web server deny rule is permanent and stops requests before PHP executes.

Can a web application firewall replace disabling XML-RPC?
A WAF can rate-limit or block xmlrpc.php, but its multicall detection varies by vendor and ruleset. Closing the endpoint at the source removes the attack surface entirely, while a WAF only filters it.

Summary

XML-RPC in WordPress is a legacy interface that most sites no longer need but still expose by default. The real risks are multicall brute force, pingback reflection and SSRF-style internal probing, and the popular xmlrpc_enabled filter handles only the first of them. Inventory your dependencies, block xmlrpc.php at the web server or allowlist known service IPs, then confirm the result from outside with a single curl request. That external check is the only proof the endpoint is actually closed.