That Jetpack Traffic Might Not Be Jetpack
Oct 12, 2026 · @Thomas J. Raef
A WordPress site was spending most of its server time answering one file, and almost every request said it came from Jetpack. It didn’t.
We found it during a review of one site’s access logs. Then we went looking for it everywhere else: 976 WordPress sites across six hosting customers. It was on almost half of them.
To be clear up front: this isn’t a flaw in Jetpack. It’s a measure of how trusted Jetpack is.
What we saw
On the first site, xmlrpc.php used about 70% of all PHP time, and about two-thirds of those requests claimed to be Jetpack. None came from Automattic. That site doesn’t even use Jetpack.
Across all 976 sites, in a single week:
- 44% of sites (429 of 976) received fake Jetpack traffic. Only 124 of the 976 use Jetpack at all.
- 93% of requests claiming to be Jetpack were fake. About 1.2 million came from outside Automattic’s network, against about 98,000 genuine ones.
- 36% of all xmlrpc.php traffic was fake Jetpack. Almost every fake request went to xmlrpc.php.
- 6,695 different IP addresses sent it. 1,344 of them (20%) showed up at more than one hosting customer, which points to one campaign working across the fleet.
- Some requests used
//xmlrpc.php. Many log tools count that as a different URL, so the attack looks smaller than it is unless you merge the two.
Our first pass got this right. (Of course) We never trust the user agent and always verify source IP address. Plenty of other tools make the mistake of no verification.
Why the disguise works
A user agent is just a line of text the client chooses to send. Anyone can type “Jetpack” into it.
Jetpack runs on more than 3 million WordPress sites, and it legitimately uses xmlrpc.php to connect them to WordPress.com. That reach is exactly why attackers borrow its name. A request that looks like Jetpack blends in with traffic site owners expect and want, so security plugins, allow rules and people reading logs by eye often wave it through.
Why it costs so much
Every xmlrpc.php request starts WordPress from scratch. No page cache helps, because each request is a POST. One request can also carry many login attempts at once. So even when every attempt fails, the server still pays full price for each one.
Built to slip past rate limits and fail2ban
Each attacking IP sends only about 17 requests a day to any one site, under one an hour. The volume comes from using thousands of IPs at once.
On the busiest server we looked at, 15 sites were each hit every day for a week, with 5,800 to 27,500 fake requests per site. Divide each site’s requests by the number of IPs sending them and you get almost the same number every time: 115 to 130 requests per IP per week. That consistency doesn’t happen by accident. The attackers are pacing each IP.
Tools like fail2ban, and most rate limits in security plugins, act when one IP fails several times within a few minutes. An IP that tries less than once an hour never reaches that line. The server pays for every request, and nothing gets banned.
They probe everything, then hammer what answers
The attackers test every site, then concentrate on the servers where xmlrpc.php responds. One hosting customer’s 12 servers showed it clearly:
| Servers | xmlrpc.php response | xmlrpc.php requests per server, one week | Fake Jetpack requests per server |
|---|---|---|---|
| 2 servers | Answered (HTTP 200) | 39,000 to 45,000 | 3,800 to 11,400 |
| 9 servers | Blocked (403 or connection closed) | 180 to 1,500 | 3 to 66 |
The blocked servers were still probed, about two fake Jetpack requests per site. But the sustained attack went where WordPress answered. Another customer blocks xmlrpc.php on every site, and it received 29 fake Jetpack requests all week.
What to do about it
Don’t count on rate limiting or fail2ban to catch this. Block xmlrpc.php at the web server, by source IP, before WordPress ever starts. Which block you need depends on one question: do you use Jetpack?
- Count your xmlrpc.php traffic first. Include the double-slash version:
grep -cE '"POST /+xmlrpc\.php' access.log
- If you don’t use Jetpack, block xmlrpc.php entirely. Modern WordPress doesn’t need it. Check first that nobody posts from an old desktop publishing tool or relies on another service that still uses XML-RPC.
- If you use Jetpack, even one feature, allow xmlrpc.php only from Automattic’s published IP ranges. Jetpack’s connection to WordPress.com runs through xmlrpc.php, so you can’t close it. You can close it to everyone else. Automattic lists its ranges on Jetpack’s IP allowlist page, and keeps a plain list at jetpack.com/ips-v4.txt for scripts and firewalls. The ranges can change, so check back now and then. Block everything else, whatever it calls itself. Jetpack publishes IPv4 ranges only, so if your server also accepts IPv6, confirm Jetpack still connects after the change.
- Test after the change. A careless nginx rule for xmlrpc.php can replace your site’s PHP handling for that URL, and real Jetpack then breaks. Confirm Jetpack still connects, and that a request from any other IP gets a 403.
Copy-and-paste rules
Pick one block for your web server. Each one stops xmlrpc.php before PHP starts, and also catches //xmlrpc.php.
nginx: paste inside the site’s server { } block, not inside a location block. On managed stacks, use your host’s include file for custom server-level config. Run nginx -t, then reload nginx.
OpenLiteSpeed or Apache: paste at the top of the site’s .htaccess, above the WordPress rules.
No Jetpack: block xmlrpc.php for everyone
nginx:
if ($uri = "/xmlrpc.php") { return 403; }
OpenLiteSpeed / Apache (.htaccess):
RewriteEngine On
RewriteRule ^/*xmlrpc\.php$ - [F,L]
Jetpack: allow xmlrpc.php only from Automattic’s IPs
nginx:
set $xmlrpc_block "";
if ($uri = "/xmlrpc.php") { set $xmlrpc_block "X"; }
if ($remote_addr !~ "^(?:192\.0\.(?:6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.|195\.234\.1(?:0[89]|1[01])\.|122\.248\.245\.244$|54\.217\.201\.243$|54\.232\.116\.4$)") { set $xmlrpc_block "${xmlrpc_block}O"; }
if ($xmlrpc_block = "XO") { return 403; }
OpenLiteSpeed / Apache (.htaccess):
RewriteEngine On
RewriteCond %{REMOTE_ADDR} !^(192\.0\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\.|195\.234\.1(0[89]|1[01])\.|122\.248\.245\.244$|54\.217\.201\.243$|54\.232\.116\.4$)
RewriteRule ^/*xmlrpc\.php$ - [F,L]
The IP patterns match Automattic’s published ranges: 192.0.64.0/18, 195.234.108.0/22, 122.248.245.244, 54.217.201.243 and 54.232.116.4. If Automattic changes its list, update the pattern.
The nginx version deliberately avoids a location = /xmlrpc.php block. That would replace your site’s PHP handling for that URL, and genuine Jetpack requests would stop working.
Behind Cloudflare or another proxy? Your server sees the proxy’s address, not the visitor’s. Configure real-IP handling first (nginx’s real_ip module, or your host’s equivalent). Otherwise the Jetpack rule blocks real Jetpack along with everything else.
We tested all four blocks on live nginx and Apache. Requests from Automattic IPs reached PHP. Requests from any other IP got a 403, including //xmlrpc.php and /xmlrpc.php?rsd. Normal pages were unaffected.
The bigger lesson
What a client says about itself is a claim, not an identity. Verify Jetpack by IP. Verify Googlebot and Bingbot with a reverse DNS lookup that resolves back to the same address. Treat a “browser” connecting from a cloud server with suspicion.
Credit where it’s due: Automattic publishes the IP ranges Jetpack uses. That’s the right way to run a service like this, and it’s what made the fakes easy to separate from the real thing.
And the volume of an attack tells you less than its shape. Thousands of IPs, each going slowly, look like nothing to a rate limiter. A block that doesn’t care how fast a request arrives, only where it comes from, is the one that holds.
