xmlrpc.php Still Reachable: Server-Level Blocking Is Mandatory

Disabling XML-RPC inside WordPress is not enough while the file still responds. Only a server-level block truly closes it.

This check follows the xmlrpc check: even when a plugin or mu-plugin disables XML-RPC logically (the xmlrpc_enabled filter), the file xmlrpc.php still loads and PHP still runs. The check simply asks: I sent a request to the file, and got a 200 or 405 response — not 403 or 404. The file is still reachable.

Why this matters

A file that returns 405 or 200 is a file the server is willing to serve. That means even when WordPress refuses to execute the requested method, it does so after a full HTTP request: PHP boots, the WordPress core loads, the database connection opens, and only then is the request rejected. An attacker running request-amplification techniques with a large-scale automated attacks attempts triggers a thousand full PHP boots. Even without successful authentication, this is heavy CPU consumption — effectively a DoS. And any future RCE in xmlrpc.php still executes because the file is loaded. A server-level block neutralizes both: the server returns 403 before PHP starts.

How to detect

curl -I https://example.com/xmlrpc.php

One of two outcomes:

  • Properly blocked: HTTP/1.1 403 Forbidden or HTTP/1.1 404 Not Found
  • Vulnerable: HTTP/1.1 200 OK or HTTP/1.1 405 Method Not Allowed

A deeper POST probe:

curl -X POST -d '<methodCall><methodName>system.listMethods</methodName></methodCall>' \
     -w "Status: %{http_code}\n" \
     https://example.com/xmlrpc.php

403 is good. An XML body with a method list, or "XML-RPC services are disabled" inside an XML envelope, means the file still loads — vulnerable.

How to fix

Apache — add to root .htaccess:

<Files xmlrpc.php>
    Require all denied
</Files>

Nginx — add to the server block (not htaccess; Nginx ignores htaccess):

location = /xmlrpc.php {
    deny all;
    access_log off;
    log_not_found off;
}

access_log off and log_not_found off matter: without them, the access log fills quickly with attack attempts. With the server returning 403 silently, the attack leaves no trace.

LiteSpeed (mostly Apache-compatible, but also via LSWS):

<Files xmlrpc.php>
    Require all denied
</Files>

Cloudflare (in front of origin): Security > WAF > Custom rules. Create a rule: (http.request.uri.path eq "/xmlrpc.php") with action Block. This blocks at the edge before the request ever reaches the server.

If a specific plugin needs XML-RPC (Jetpack in older configs), allow only authorized IP ranges:

<Files xmlrpc.php>
    Require ip 192.0.64.0/18
    Require ip 76.74.255.0/24
</Files>

Common mistakes

Mistake one: trusting a "Disable XML-RPC" plugin. Most only add a PHP filter; they do not block at the web server layer. After the plugin is active, the file still returns 200 or 405 instead of 403. Always verify with curl. Mistake two: putting the rules in .htaccess on an Nginx server. Nginx does not read htaccess — your rules sit there with no effect. Ask the host which server is actually serving requests. Mistake three: blocking at origin without addressing a fronting WAF/CDN. If Cloudflare passes the request to origin and the origin blocks it, that is functional — but ideally block at the edge to save bandwidth and CPU. Mistake four: testing right after the change instead of after 24 hours. A page cache like LiteSpeed Cache sometimes stores xmlrpc.php responses; the first curl will show 200 even after blocking until the cache expires.

Verifying the fix

Run both curl commands from "How to detect" again. Both must return 403 (Apache/Cloudflare) or 444/404 (Nginx). If you still see 200 or 405, the rule did not load. Verify config: apachectl configtest or nginx -t, then systemctl reload apache2 or nginx -s reload. Wait five minutes and retest. Finish by running the audit and confirming both xmlrpc and xmlrpc_reachable pass.

Tip: If XML-RPC is blocked and you use Cloudflare, add a "Block xmlrpc.php" WAF rule. That cuts 100% of XML-RPC requests before they ever reach origin — a meaningful CPU saving during brute-force campaigns.