wpmgr
SecurityWPMgr Team

How to Harden Your WordPress Login Page

Practical steps to cut automated login attacks on WordPress: rate limiting that survives a PHP error, closing XML-RPC multicall, and stopping user enumeration.

Every WordPress site on the public internet receives automated login traffic. Not because anyone chose it, but because wp-login.php sits at a predictable address on millions of hosts, and testing credential lists against a predictable address is cheap.

A stock install gives that traffic an unlimited runway. There is no attempt limit, the username is often discoverable, and a second protocol on the same host will happily accept hundreds of credential guesses in a single request. Closing those gaps is among the highest-return security work available, because it removes a whole class of attack before it reaches your application code.

Why the login page is the default target

Three properties make it attractive.

It is findable. WordPress announces itself in page source, in HTTP headers, in the readme file, and through the REST API. Fingerprinting a WordPress install takes one request.

It is uniform. The login form is at the same path, takes the same field names, and returns distinguishable responses across the entire ecosystem. One script works everywhere.

It is chatty. By default WordPress tells an attacker whether the username was wrong or the password was wrong. That turns one guess into two pieces of information, and it means an attacker can confirm a valid username before spending a single attempt on passwords.

The damage is not limited to a successful login either. Sustained attempts consume PHP workers, inflate hosting bills on metered plans, and bury real security events in log noise. Plenty of sites that were never breached have still been taken down by the load of people trying.

Limit attempts, at the right layer

WordPress permits unlimited login attempts. Capping failures per IP address, somewhere around three to five per hour, stops most credential stuffing with no effect on legitimate users, who almost never fail five times in an hour.

Where the limit is enforced matters more than the number. A limiter implemented purely in PHP inside WordPress is bypassed whenever the request does not complete normally: if the site throws a fatal error during login, the attempt may never be recorded, and an attacker who can reliably trigger an error gets free guesses. WPMgr enforces limits through the agent at a layer that still counts the attempt when PHP returns a 500.

The second thing worth having is a view across sites. A single site showing forty failures an hour is background noise. The same forty against one specific site while the rest of your fleet sees nothing is somebody who has picked a target, and that distinction only exists if the counts are aggregated centrally.

Close XML-RPC, or at least close multicall

XML-RPC is a legacy remote procedure protocol that WordPress still exposes at /xmlrpc.php. It matters here for one reason: the system.multicall method batches many calls into a single HTTP request.

That single property defeats naive rate limiting entirely. If your limiter counts requests, and one request carries three hundred credential pairs, the attacker gets three hundred guesses for the price of one. Everything you did to the login form is irrelevant while this endpoint is open.

Some things still need it. Jetpack uses it, as do older mobile apps and some remote publishing tools. Most modern setups do not. If nothing depends on it, disable it. If pingbacks are the only thing you want, a pingbacks-only mode keeps those working while removing the multicall surface. WPMgr exposes this per site from the Security tab so you can make the decision once and apply it everywhere.

Stop the site from confirming usernames

An attacker needs a username and a password. WordPress is unhelpfully generous with the first half.

Author archives. Requesting /?author=1 redirects to that user's archive, and the resulting URL contains their login slug. Walking the integers enumerates the whole user table.

The REST API users route. /wp-json/wp/v2/users returns display names, slugs, and IDs to unauthenticated requests on a default install.

Login error messages. "Unknown username" and "The password you entered for the username admin is incorrect" are different messages, and the difference is the answer.

Display names. WordPress shows the display name on posts and comments, and by default the display name is the username. If your byline reads sarah, that is the login.

Fix all four. Block author enumeration redirects, restrict the users route to authenticated requests, and force display names that differ from login names. WPMgr's Security tab covers the first three without code, and the display name rule is worth enforcing as policy because it costs nothing.

Restricting the REST API needs a little judgement. Some themes and plugins genuinely require unauthenticated access to specific namespaces, so blanket-blocking /wp-json/ can break a site in ways that are not obvious immediately. Restricting the user-listing routes specifically is the high-value, low-risk version.

Moving the login URL: useful, but be honest about it

Relocating wp-login.php to a custom path removes your site from the reach of scanners that only know the default address. In practice that is a large share of the automated traffic, and the drop in log noise is immediate and real.

It is not a defence. The page is still there, still reachable by anyone who finds it, and various things leak the new location if you are careless. Treat it as reducing volume rather than reducing risk, and do not let it substitute for attempt limiting or a second factor.

One practical caution: whatever manages your sites needs to keep working after the move. WPMgr reaches sites over a signed token path rather than the login form, so hiding the login does not lock the control plane out. Anything that automates through the login form will break.

Add a second factor

Everything above reduces the number of guesses an attacker gets. A second factor changes the game, because it makes the password insufficient even when correct.

This matters because the most likely way somebody gets a valid password for your site is not guessing it. It is reuse. A password exposed in an unrelated breach, tried against your login, works on the first attempt, and no rate limiter fires because there were no failures.

Require it for administrators at minimum, and for editors on any site where content is revenue. Authenticator apps (TOTP) are the sensible default; email one-time codes are weaker, because they inherit the security of the mailbox, but they are far better than nothing and much easier to roll out to non-technical clients.

Roll it out with a grace period rather than overnight. WPMgr lets you require 2FA per role, per site, with a configurable window for existing users to enrol, which is the difference between a policy that lands and one that generates twenty support emails on a Monday.

Restricting wp-admin at the web server

A layer above WordPress entirely: require HTTP authentication or an IP allowlist on /wp-admin and /wp-login.php at the web server. A request that fails there never reaches PHP, so it costs you nothing to serve and cannot exploit anything in the application.

It is the strongest option on this page and the least often usable. An IP allowlist needs everyone who administers the site to have a stable address, which rules out most agencies with remote staff and anyone on mobile networks. HTTP auth adds a second prompt that clients find confusing and will ask you to remove.

Where it fits well is sites with a small, fixed set of administrators and no client logins: your own properties, internal tools, and staging environments. Staging in particular is worth doing every time, because staging sites are routinely left with weak passwords, real data, and no monitoring.

One caveat: blocking /wp-admin wholesale breaks admin-ajax.php, which plenty of front-end features on public pages call without a login. Exempt that file, or expect intermittent breakage that is difficult to trace.

Sessions and application passwords

Two smaller surfaces worth closing once the main ones are done.

Sessions outlive password changes unless you end them. Changing a compromised password does not, by itself, log out an attacker who already has a valid session cookie. WordPress can destroy other sessions for a user, and doing so should be part of any credential rotation. Rotating the salts in wp-config.php invalidates every session on the site at once, which is the blunt version and is the right move after a confirmed compromise.

Application passwords bypass two-factor authentication by design. They exist so that external tools can authenticate to the REST API, and they are not covered by your 2FA policy. That is correct behaviour and also means an attacker who creates one has a durable credential that survives a password reset. Audit the application passwords on each account, revoke ones you cannot account for, and disable the feature entirely on sites where nothing uses the REST API for authenticated work.

Two things people skip

Delete dormant administrator accounts. The developer who built the site three years ago, the agency you stopped working with, the plugin that created a service account. Each is a full-privilege credential you are not watching. Audit the admin list on every site and reduce it to people who need it this quarter.

Do not reuse admin credentials across a portfolio. One password across fifty client sites means one breach is fifty breaches. This is the single most common structural weakness in agency setups, and it turns a contained incident into a catastrophic one.

Doing it across a fleet

None of these steps is difficult on one site. The difficulty is applying all of them to every site, and knowing which ones drifted.

That is the actual failure mode in an agency: the hardening was done, correctly, at launch, and then a site was migrated, or restored from an old backup, or handed over, and quietly reverted. Nobody notices until it matters.

Push the controls from one place, and check them from one place. WPMgr sends this configuration from the control plane to the agent on each site, so hardening fifty sites takes about as long as hardening one, and the fleet view shows you where the settings do not match what you intended.

For the full set of controls, see the Security suite feature page. For how login hardening fits alongside vulnerability scanning and file integrity checks, see the WordPress security solution guide.

More in this category

All Security articles

Related feature

WordPress Security Suite

Solution guide

WordPress security

Manage your WordPress fleet with one open-source dashboard.

Free, self-hostable, no per-site fee. Read every line before you run it.