How to block xmlrpc.php brute force attacks on WordPress
Last updated:
xmlrpc.php is WordPress's old remote API. Few sites still need it, and every WordPress site on the internet gets attacked through it. Its system.multicall method runs many calls in a single request, so one POST can try hundreds of passwords, which is why login limiters that count requests to wp-login.php do not see it. Its pingback.ping method is used to make your site send requests to someone else's.
Below: how to recognize the attack in the access logs, how to block xmlrpc.php in nginx, Apache or WordPress itself, what to do if you use Jetpack, and how to rate-limit or ban with fail2ban when you must keep it open. Commands are for Debian and Ubuntu and for the RHEL family; example.com stands for your site.
Recognize an XML-RPC attack in the access logs
Count the POST requests to xmlrpc.php per address. A normal site sees a few a day, from Jetpack or an app. Hundreds from one address, or a steady stream from many, is an attack.
sudo sh -c "grep -hI 'POST /xmlrpc.php' /var/log/nginx/*access*.log /var/log/apache2/*access*.log \
/var/log/httpd/*access_log /usr/local/apache/domlogs/* /var/log/apache2/domlogs/* 2>/dev/null" \
| awk '{print $1}' | sort | uniq -c | sort -rn | head -20The command reads the usual places: nginx, Apache on Debian and Ubuntu, httpd on the RHEL family, and cPanel's per-domain logs. The requests per hour show when it started:
sudo sh -c "grep -hI 'POST /xmlrpc.php' /var/log/nginx/access.log /var/log/apache2/access.log /var/log/httpd/access_log 2>/dev/null" \
| awk '{print substr($4, 2, 14)}' | uniq -c | tail -n 24- The status is
200even for a wrong password: XML-RPC reports the failure inside the response. Do not read200as a successful login. - Large responses, repeated from one address every few seconds, are typically
system.multicall: one answer for many password attempts. - The log does not show which method was called: the request body is not logged. Brute force and pingback abuse look the same here.
To see whether XML-RPC answers on a site, ask it for its method list from any machine. A list of methods means it is on; a 403 means it is blocked.
curl -s -X POST -H 'Content-Type: text/xml' \
-d '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName></methodCall>' \
https://example.com/xmlrpc.php | head -c 400; echoBlock xmlrpc.php in nginx or Apache
Blocking at the web server is the cheapest option: the request is refused before PHP and WordPress load. In nginx, add this to the server block of each WordPress site:
# In the site's server { } block, before the "location ~ \.php$" block
location = /xmlrpc.php {
deny all;
}sudo nginx -t && sudo systemctl reload nginxIn Apache 2.4, put this in the site's .htaccess (no reload needed; LiteSpeed reads it too) or in its <VirtualHost>:
# In the site's .htaccess or <VirtualHost>
<Files "xmlrpc.php">
Require all denied
</Files>sudo apachectl configtest && sudo systemctl reload apache2sudo apachectl configtest && sudo systemctl reload httpdWarning: Blocking xmlrpc.php breaks what still uses it: Jetpack (see below), pingbacks, and some older apps and publishing tools. If you are not sure, check the logs from step 1 for legitimate callers first.
Turn off XML-RPC inside WordPress
Where you cannot change the web server configuration, a must-use plugin does it from WordPress. Must-use plugins load on every request and cannot be turned off from the admin area by mistake. Replace the path with your site's:
sudo mkdir -p /var/www/example.com/wp-content/mu-plugins
sudo tee /var/www/example.com/wp-content/mu-plugins/disable-xmlrpc.php > /dev/null <<'EOF'
<?php
// Turns off the XML-RPC methods that need a login, and pingbacks.
add_filter( 'xmlrpc_enabled', '__return_false' );
add_filter( 'xmlrpc_methods', function ( $methods ) {
unset( $methods['pingback.ping'], $methods['pingback.extensions.getPingbacks'] );
return $methods;
} );
EOFNote: The xmlrpc_enabled filter only turns off the methods that need a login, as the WordPress reference says; the second filter removes the pingback methods. PHP still runs for every request, so under a heavy attack the web server block above is lighter.
Plugins do the same: dedicated "Disable XML-RPC" plugins, and security plugins such as Wordfence and Solid Security have an option for it (as of November 2026). A plugin is easy to switch off by accident during an update; the mu-plugin and the web server rule are not.
Keep Jetpack working
Jetpack talks to your site through xmlrpc.php, so blocking it for everyone breaks the Jetpack connection. Jetpack publishes the address ranges its servers use on its allowlist page: allow those and block everyone else. In nginx the location must then also pass the request to PHP:
location = /xmlrpc.php {
# Jetpack's ranges, from its allowlist page (check it for changes)
allow 122.248.245.244/32;
allow 54.217.201.243/32;
allow 54.232.116.4/32;
allow 192.0.64.0/18;
allow 195.234.108.0/22;
deny all;
# then the same lines as in your "location ~ \.php$" block, for example:
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php-fpm.sock;
}Copy the PHP lines from the site's own location ~ \.php$ block: the socket path differs between systems. In Apache, Require ip alone allows only those ranges:
<Files "xmlrpc.php">
Require ip 122.248.245.244/32 54.217.201.243/32 54.232.116.4/32 192.0.64.0/18 195.234.108.0/22
</Files>Note: Ranges as listed by Jetpack in November 2026. They can change: check the page from time to time. Behind Cloudflare or another proxy, these rules see the proxy's address unless the real visitor address is restored first (nginx real_ip, Apache mod_remoteip).
Rate-limit xmlrpc.php when it must stay open
If an app or service you cannot list by address needs XML-RPC, limit how often each address may call it. In nginx, define a zone in the http context, then use it for xmlrpc.php: here 6 requests a minute per address, with a burst of 5, and 429 beyond that.
# /etc/nginx/conf.d/xmlrpc-limit.conf (read inside the http { } block)
limit_req_zone $binary_remote_addr zone=xmlrpc:10m rate=6r/m;# In the site's server { } block
location = /xmlrpc.php {
limit_req zone=xmlrpc burst=5 nodelay;
limit_req_status 429;
# then the same lines as in your "location ~ \.php$" block, for example:
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php-fpm.sock;
}sudo nginx -t && sudo systemctl reload nginxNote: A rate limit counts requests, not passwords: one system.multicall request inside the limit can still carry hundreds of attempts. Pair it with strong passwords, or ban the addresses with fail2ban. Apache has no built-in equivalent; use fail2ban below.
Ban XML-RPC attackers with fail2ban
fail2ban reads the access log and bans an address at the firewall, so it works with nginx and Apache alike. Install it as in How to stop SSH brute force attacks (Debian and Ubuntu repositories, EPEL on the RHEL family). Then create a filter for POST requests to xmlrpc.php:
sudo tee /etc/fail2ban/filter.d/wordpress-xmlrpc.conf > /dev/null <<'EOF'
[Definition]
failregex = ^<HOST> \S+ \S+ \[[^]]+\] "POST /xmlrpc\.php
ignoreregex =
EOFAnd a jail: 5 requests in 60 seconds, a ban of one hour. Put your own address in ignoreip, and, if you use Jetpack, its ranges. For Apache, set logpath to /var/log/apache2/*access.log on Debian and Ubuntu or /var/log/httpd/*access_log on the RHEL family.
sudo tee /etc/fail2ban/jail.d/wordpress-xmlrpc.local > /dev/null <<'EOF'
[wordpress-xmlrpc]
enabled = true
port = http,https
filter = wordpress-xmlrpc
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 60
bantime = 3600
ignoreip = 127.0.0.1/8 ::1 192.0.2.10
EOFTest the filter on the real log before you enable it: fail2ban-regex shows how many lines match. Then restart fail2ban and check the jail.
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-xmlrpc.conf
sudo systemctl restart fail2ban
sudo fail2ban-client status wordpress-xmlrpcWarning: Behind Cloudflare or another proxy, the access log shows the proxy's address: fail2ban would ban the proxy and take the site offline for many visitors. Restore the real address in the log first, or block at the proxy.
How InfraFence handles XML-RPC attacks
InfraFence's web firewall reads the access logs of nginx, Apache and LiteSpeed, and from agent 1.0.42 also of Traefik, Caddy, HAProxy and web servers in containers. Behind Cloudflare it takes the visitor address from CF-Connecting-IP, and only from Cloudflare's own ranges.
- Every 5 POST requests to
xmlrpc.phpfrom one address on one site within a minute add 25 points to that address's score. A sustained attack keeps adding them. - The score also collects the address's other attacks, such as brute force on
wp-login.php, plugin scanning or exploit attempts, and decays by 5 points a minute, so a single odd request fades away. - At 80 points the address is banned at the firewall and the event is reported as critical. Thresholds and points can be changed per server, and monitor mode reports without banning.
See Web firewall for the rules and the score. What it does not do: it counts requests, so it cannot see how many passwords a system.multicall request carries; it does not change your nginx, Apache or WordPress configuration, so xmlrpc.php stays open until you block it as above; and it has no built-in exception for Jetpack: if Jetpack's servers call often enough to be scored, add their ranges to the whitelist. The ban comes from the log, so the request that triggered it has already been answered.
Questions
- Is it safe to disable xmlrpc.php?
- For most sites, yes: the block editor and the admin area use the REST API, not XML-RPC. Check first whether you use Jetpack, pingbacks, an app or a publishing tool that still relies on it.
- Why do the attacks show status 200?
- XML-RPC answers
200and reports the failed login inside the response. The status code says nothing about whether a password worked. - My login limiter plugin does not stop it. Why?
- Many limiters count attempts on
wp-login.php. Asystem.multicallrequest throughxmlrpc.phpcarries hundreds of attempts in one request and goes around them. Block or limitxmlrpc.phpitself.
Try it free on 1 server
Starter is free for one server, with full protection and automatic blocking. No credit card.
Install the agent as root, with your token in place of <TOKEN>:
curl -fsSL https://github.com/infrafence/infrafence-agent/releases/latest/download/install.sh | sudo bash -s -- --token <TOKEN>The token is created in the dashboard, in Servers › Add server.
