DEV Community

Calin V.
Calin V.

Posted on Originally published at wpghost.com

Blocking xmlrpc.php on Apache and Nginx, then testing it with curl

xmlrpc.php sits in the WordPress root and has answered requests by default since WordPress 3.5. The dashboard has no switch for it.

Most guides stop at a one-line PHP filter. That filter leaves part of the file working. The server rules below close the whole file, and the curl commands at the end show whether the block holds.

What the PHP filter turns off

The usual snippet goes in a plugin or functions.php:

add_filter( 'xmlrpc_enabled', '__return_false' );
Enter fullscreen mode Exit fullscreen mode

The WordPress developer reference for xmlrpc_enabled says the filter "does not control whether XML-RPC is fully enabled". It switches off the methods that need a login, like publishing.

Pingbacks keep working. pingback.ping needs no login, so the filter leaves it alone.

PHP also boots for every request to the file. xmlrpc.php loads WordPress before it reads the filter, so each bot request still costs a full page load on the server.

If a PHP filter is all a host allows, add a second one that drops the two pingback methods:

add_filter( 'xmlrpc_methods', function ( $methods ) {
    unset( $methods['pingback.ping'] );
    unset( $methods['pingback.extensions.getPingbacks'] );
    return $methods;
} );
Enter fullscreen mode Exit fullscreen mode

Apache and LiteSpeed

Add this to .htaccess in the site root:

<Files xmlrpc.php>
    Require all denied
</Files>
Enter fullscreen mode Exit fullscreen mode

Require all denied is Apache 2.4 syntax. Apache 2.2 needs Order Deny,Allow and Deny from all instead.

The request gets a 403 before PHP starts.

Nginx

Put this inside the server block:

location = /xmlrpc.php {
    deny all;
}
Enter fullscreen mode Exit fullscreen mode

Test the config with nginx -t and reload with systemctl reload nginx.

An exact-match location = wins over regex locations in Nginx, so PHP-FPM never sees the request.

Keeping Jetpack connected

Jetpack's support page on IP allowlists says Jetpack relies on xmlrpc.php to talk to WordPress.com. The same page warns that blocking the file stops the connection from working properly.

Jetpack publishes its ranges as plain text at https://jetpack.com/ips-v4.txt and as JSON at https://jetpack.com/ips-v4.json.

On Apache 2.4, turn the list into Require ip lines:

curl -s https://jetpack.com/ips-v4.txt | sed 's/^/    Require ip /'
Enter fullscreen mode Exit fullscreen mode

Paste the output between <Files xmlrpc.php> and </Files> in place of Require all denied. Apache then answers those ranges and refuses every other address.

On Nginx the same list becomes allow lines, and deny all; goes last:

curl -s https://jetpack.com/ips-v4.txt | sed 's/^/    allow /; s/$/;/'
Enter fullscreen mode Exit fullscreen mode

Re-run the command whenever the Jetpack connection test fails after a server change.

At the edge

On Cloudflare, a custom rule with this expression and the Block action stops the request before it reaches the origin:

(http.request.uri.path eq "/xmlrpc.php")
Enter fullscreen mode Exit fullscreen mode

A site that runs Jetpack needs an exception for Jetpack's ranges in that rule too.

Check the block with curl

A browser visit to an open xmlrpc.php prints XML-RPC server accepts POST requests only. Bots send POST, so test with POST:

curl -s -o /dev/null -w "%{http_code}\n" -X POST \
  -H "Content-Type: text/xml" \
  --data '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName></methodCall>' \
  https://example.com/xmlrpc.php
Enter fullscreen mode Exit fullscreen mode
Status Meaning
200 The file still answers, even with the PHP filter on
403 The Apache or Nginx rule works
404 Something earlier in the stack refuses the path

On a site that can only use the PHP filters, drop the -o /dev/null part and look for pingback methods in the reply:

curl -s -X POST -H "Content-Type: text/xml" \
  --data '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName></methodCall>' \
  https://example.com/xmlrpc.php | grep -c pingback
Enter fullscreen mode Exit fullscreen mode

0 means the xmlrpc_methods filter removed them.

Count the attempts in the access log

In the default combined log format, field 7 is the path and field 9 is the status code:

grep -c 'POST /xmlrpc.php' /var/log/nginx/access.log
awk '$7 == "/xmlrpc.php" {print $9}' /var/log/nginx/access.log | sort | uniq -c
Enter fullscreen mode Exit fullscreen mode

On Debian and Ubuntu, Apache writes to /var/log/apache2/access.log.

Before the rule the hits show as 200. After it they show as 403.

What the block does not cover

wp-login.php takes password attempts on its own route. It still needs a rate limit after xmlrpc.php is closed.

The Verizon 2025 Data Breach Investigations Report found stolen credentials in 88% of basic web application attacks.

Run the POST curl command against a production site today and note the status code. Has anyone found a client besides Jetpack that still needs xmlrpc.php in 2026?

Top comments (0)