WordPress is flooded with password guessing
How to cut off password guessing against wp-login.php and xmlrpc.php and plugin scans before they reach PHP, and close the server to direct access.
Password guessing against wp-login.php and xmlrpc.php and plugin scans load the
server even with a security plugin installed: the plugin works inside WordPress, so by
the time it refuses, the request has already started PHP.
The fix is to cut this traffic off before the server. The site is connected through WebShield, junk requests stop with us, and the server is closed to direct access.
What you get: the site runs through WebShield; scanners, password guessing and
injections stop with us, and visitors reach the server. Direct access to the server
around the protection is closed.
You need: access to the domain registrar and to the server — the web server config or
at least wp-config.php. Time: about an hour, plus waiting for the nameserver change.
1. Connect the domain
Section titled “1. Connect the domain”Control panel → Domains → Add domain, enter example.com. Keep record import on and
compare the list found with your current zone. Pay special attention to mail: MX,
SPF, DKIM, DMARC. If a record was not found, add it by hand before switching
nameservers.
Then at the registrar replace the nameservers with ours:
nsbox.webshield.pronshub.webshield.proWhile the change spreads, everything works as before. When the domain status turns Delegated, move on. Details and AXFR import are in the reference Transfer domain control.
Can’t or don’t want to move DNS? The site connects with records at your provider too — see Connect without changing nameservers; the rest is the same.
2. Route the site through the protection
Section titled “2. Route the site through the protection”- DNS → turn on Proxy for the
example.comandwwwrecords. - Hosts → for each of the two names turn on Require SSL and Redirect HTTP to HTTPS. We issue the certificate ourselves within a couple of minutes.
Your server stays as it is and keeps answering on port 443: we reach it over HTTPS too, and the certificate already installed on it will do.
Check:
curl -sI https://example.com/ | grep -i '^server'server: WebShield.Pro means the request went through us.
From this moment, with no settings at all, you get signatures against SQL injection, XSS
and path traversal, known scanners filtered out, rate limits on login forms, the admin
area and xmlrpc.php, and addresses that keep scanning the site get banned on all our
nodes.
3. Give WordPress the real visitor addresses
Section titled “3. Give WordPress the real visitor addresses”Now our nodes connect to the server, and WordPress sees their addresses instead of the visitors’. Comments, logs and security plugins get confused. Login limiters are the worst: by blocking the “offender” such a plugin blocks our node — and everyone who comes through it.
We pass the real address in the X-Real-IP header. You can trust it only when the
request really came from us, otherwise anyone could forge an address. So first take the
host secret: Hosts → host → Access only through WebShield → Value. Then add
to wp-config.php above the line /* That's all, stop editing! */:
// Visitor address from WebShield — only for requests that came through us.if (isset($_SERVER['HTTP_X_WEBSHIELD_ORIGIN_TOKEN'], $_SERVER['HTTP_X_REAL_IP']) && hash_equals('wso_YOUR_TOKEN', $_SERVER['HTTP_X_WEBSHIELD_ORIGIN_TOKEN'])) { $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_REAL_IP'];}example.com and www have different secrets. If www only redirects to the main name,
the main name’s secret is enough.
4. Close the server to direct requests
Section titled “4. Close the server to direct requests”The server’s address has not gone anywhere: it sits in old DNS records, in archives and in scanner databases. Anyone who knows it reaches the site directly — around everything you have just set up. So the server must answer only requests carrying the same secret.
For nginx, in the server block of your site:
if ($http_x_webshield_origin_token != "wso_YOUR_TOKEN") { return 403; }For Apache, at the top of .htaccess in the WordPress root:
<If "%{HTTP:X-WebShield-Origin-Token} != 'wso_YOUR_TOKEN'"> Require all denied</If>Make the change in the web server, not in PHP: a WordPress update won’t overwrite it, and a refusal won’t cost a PHP start. Before that, make sure the site opens through us (step 2), and keep SSH or the hosting panel at hand. More, including other servers and automatic bans with fail2ban, is in the guide Attacks straight at the server IP.
Check:
# Directly — expect 403.curl -sk -o /dev/null -w '%{http_code}\n' https://SERVER_ADDRESS/ -H 'Host: example.com'# Through us — 200.curl -s -o /dev/null -w '%{http_code}\n' https://example.com/5. Close xmlrpc.php
Section titled “5. Close xmlrpc.php”xmlrpc.php is an old remote publishing interface, used for example by Jetpack and some
mobile apps. Its system.multicall method lets a single request try many passwords. If you don’t use it, close it on the server:
location = /xmlrpc.php { return 403; }<Files "xmlrpc.php"> Require all denied</Files>6. Turn on the browser check — when you need it
Section titled “6. Turn on the browser check — when you need it”Everything above is invisible to visitors. If the attack keeps going — a botnet guessing passwords from thousands of addresses, each knocking rarely — set Bot protection → Browser check on the host. A visitor sees the check screen for a second, and bots go no further. Known search engine crawlers are not held up.
The check also stops your site’s own useful robots. Before turning it on, add to Check exclusions on the host page:
/wp-cron.php— WordPress runs background jobs by requesting its own address; without the exclusion scheduled posts won’t go out and emails won’t be sent;- the payment notification address — the payment plugin settings tell you what it is.
An address like
/?wc-api=…cannot be excluded (that is the site root); switch the plugin to an address like/wc-api/…; /wp-json/— if a mobile app or external services work with the site through the REST API.
Signatures, limits and bans keep working on excluded paths — only the check screen is lifted.
- In Statistics, on the Protection tab, “Attacks repelled” and “IPs banned” go up.
- The server log no longer shows
wp-login.phprequests from hundreds of addresses or requests for plugins that don’t exist. - CPU load has gone down.
When something is off
Section titled “When something is off”| Symptom | Cause and fix |
|---|---|
| The site answers 502 | The server does not answer us on port 443. Check that HTTPS works on it, or turn off Redirect HTTP to HTTPS so we reach it over HTTP. |
| Endless redirect | WordPress has a site address starting with http://, while we redirect to HTTPS. In Settings → General use https:// addresses. |
| After step 4 the site answers 403 to everyone | The token in the config does not match the panel or belongs to another host. Compare the value. |
| The security plugin blocked everyone | Step 3 was skipped or done after the plugin had already banned our node’s address. Add the code and clear the plugin’s block list. |
| Scheduled posts don’t go out | The browser check is on and /wp-cron.php is not excluded. |
| Payments don’t go through | Payment notifications hit the browser check. Add their address to the exclusions. |
What next
Section titled “What next”- CDN and protection — caching, HTTP/3 and other host settings. Page caching is safe to turn on: logged-in users and shoppers with items in a WooCommerce cart bypass it.
- Attacks straight at the server IP — what else gives the server’s address away.
- Site under attack — if everything is down right now.