WP Security: wp-admin Security | Change the Login URL, Limit Login Attempts, XML-RPC

Affiliate disclosure v3.1
Affiliate disclosure v3.2 (mobile)

wp-admin security means the settings that block brute-force attacks coming in through the WordPress admin screen and login screen. After checking the access logs of a Nginx + Apache server running several WordPress sites, automated requests kept arriving at xmlrpc.php even after the login URL was changed, so I added a server-level rate limit as well.

1️⃣ Why wp-admin and wp-login.php get attacked

The fact that the WordPress admin URL is public knowledge is a weakness, but stronger security can make up for it. Every WordPress site uses the same /wp-admin/ and [wp-login.php], and opening /wp-admin/ while logged out takes you to the [wp-login.php] login screen.

Attacks come in as brute force, entering usernames and passwords on this login screen over and over. That is why wp-admin security is first about stopping login attempts, not hiding the URL.

Two ways into a WordPress login
Visit in a browser
/wp-admin/
If logged out, redirects to the login screen (302)
wp-login.php
Login screen · enter username and password
Apps · automated requests
xmlrpc.php
Sends username and password in a POST request without the login screen
Skips the login screen
system.multicall tries several passwords in one request · the URL stays the same even if you change the login URL
Same username and password check
Both entrances check the account with the same WordPress authentication function
Brute-force attacks come in through whichever of the two entrances is open. Changing the login URL only applies to the upper entrance, so xmlrpc.php has to be closed or rate-limited separately.

2️⃣ What changing the login URL does and does not do

Changing the login URL with a security plugin makes the default [wp-login.php] return a 404, so automated attacks hammering the default URL never reach the login screen. You have to tell the new URL separately to everyone with an admin account.

🧭 WordPress login entrances
Explainer diagram
⬜ No plugin
🛡️ Hide My WP
🚪 Login entrance
/wp-login.php · /wp-admin/Same URL on every WordPress
Login screen404
/newloginChanged URL · only admins know it
Login screen
POST /xmlrpc.phpChecks the password without a login screen
Answers the password checkDisabled 405
Measured on a test site · requests while logged out
→
🤖 Automated attacks
Entrances where passwords can be tried at a known URL
2
1
None
Without a plugin, passwords can be tried at two places: the same [wp-login.php] every WordPress site has, and [xmlrpc.php], which checks passwords without a login screen.
After changing the login URL, [wp-login.php] and /wp-admin/ return 404, but [xmlrpc.php] still answers whether the username and password are correct.
With XML-RPC turned off as well, login requests to [xmlrpc.php] end with a reply that the XML-RPC service is disabled, so passwords can no longer be tested at a known URL.

This method takes little effort, but if you forget the new URL you cannot get into the admin screen and have to rename that plugin's folder via FTP or a file manager to deactivate it. On a site managed by several people, it is best to record the URL in one place.

3️⃣ Limiting login attempts and two-factor authentication

Even after changing the login URL, once the new URL leaks the attempts to guess passwords continue. WordPress core has no limit on failed logins, so no matter how many wrong passwords are entered, the same login screen opens again.

🧭 Limiting login attempts and two-factor authentication
Explainer diagram
⬜ No plugin
🛡️ Security plugin
🔑 Logins from the same IP
1stWrong password
Login screen again3 left
2ndWrong password
Login screen again2 left
3rdWrong password
Login screen again1 left
4thWrong password
Login screen againBlocked 20 min
5thBlockedWrong passwordLogin · XML-RPC requests
Login screen againxmlrpc.php also 403
Enter passwordCorrect password
Code entry screen
Authentication Code6 digits from the authenticator app · changes every 30 s
Admin screens
Attacker who only knows the passwordDoes not know the code
Stops at the code screen
Measured on a test site · consecutive requests from the same IP
→
🤖 Automated attacks
Can passwords keep being tested?
No limit
Blocked on the 4th
Stops at the code
Without a plugin, entering a wrong password six times from the same IP opens the same login screen every time.
With login attempt limits on, every wrong try shows how many attempts are left, and after the 4th failure that IP cannot log in for 20 minutes. While blocked, xmlrpc.php requests also end in 403.
With two-factor authentication on, even a correct password only opens the admin screen after entering the 6-digit [Authentication Code] that the authenticator app generates every 30 seconds.

A login attempt limit plugin blocks an IP for a set time once it exceeds the allowed number of tries, and two-factor authentication asks for a code from the authenticator app on your phone even when the password is correct. When you turn on two-factor authentication, also keep backup codes for when you lose your phone.

4️⃣ Closing xmlrpc.php

[xmlrpc.php] is a second entrance that takes a username and password without going through the login screen, and with the [system.multicall] method it can test several passwords in one request.

The access logs of a Nginx + Apache server running several WordPress sites showed 2,250 POST requests to [xmlrpc.php] between midnight and 9:50 a.m. on October 13. The requests came from 22 IPs, and their User-Agent posed as Jetpack and WordPress.com.

If you do not use the WordPress mobile app or Jetpack, block [xmlrpc.php] requests in the Nginx configuration or use the XML-RPC off setting of a security plugin.

# Inside the server block — deny xmlrpc.php requests
location = /xmlrpc.php {
    deny all;
}

With this setting, GET and POST requests to [xmlrpc.php] all end in 403 without reaching PHP. A security plugin's XML-RPC off option only rejects requests that need a login while the file keeps responding, so to cut PHP executions too, blocking in Nginx is the better fit.

5️⃣ Blocking at the server: admin IP restriction and rate limiting

If you only manage the site from a fixed IP, allowing [wp-login.php] and /wp-admin/ only for that IP in Nginx is the most reliable method. It does not suit setups where your home or office IP changes often.

Blocking all of /wp-admin/ may seem enough, but visitor-facing features such as search or a shopping cart often send requests to [admin-ajax.php], so that file has to stay open.

# Inside the server block — above the PHP location
location = /wp-login.php {
    allow 203.0.113.10;   # Your fixed admin IP
    deny all;
    # Copy the settings of your PHP location below this line
}

Opening [wp-login.php] from an IP other than the allowed one returns a 403, while the allowed IP gets the login screen. Inside the location you have to copy the site's PHP handling settings (fastcgi_pass and so on) as they are for the login screen to run normally.

If you cannot fix your IP, you can put a rate limit on PHP requests from visitors who are not logged in; the process of limiting them to 30 per minute so automated requests cannot hog server resources is covered in the php-fpm max_children warning article.

# http block — count only requests without a login cookie, per IP
map $http_cookie $php_anon_key {
    default                 $binary_remote_addr;
    "~*wordpress_logged_in" "";
}
limit_req_zone $php_anon_key zone=php_anon:10m rate=30r/m;

# Inside the PHP location
limit_req zone=php_anon burst=60 nodelay;
limit_req_status 429;

When a single logged-out IP sends PHP requests in a row, up to the 61st are handled and after that 429 is returned; requests with a login cookie are not counted. If page caching answers without going through PHP, normal visitors viewing pages never hit this limit.

ℹ️ Affiliate Disclosure
This site's content contains affiliate links. When a visitor buys a product or service through one of them, the site receives a commission from the seller. The amount the buyer pays(it goes down during event discounts ↓)does not go up. Posted prices, discounts, and stock reflect the time of writing and may differ, so confirm with the seller before buying. Products are chosen and reviewed by our own standards, and commissions do not affect the order or content of reviews.

Leave a Reply

Your email address will not be published. Required fields are marked *

Prove your humanity: 5   +   7   =