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' );
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;
} );
Apache and LiteSpeed
Add this to .htaccess in the site root:
<Files xmlrpc.php>
Require all denied
</Files>
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;
}
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 /'
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/$/;/'
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")
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
| 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
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
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)