How to stop spam user registrations in WordPress
Unchecking "Anyone can register" is step one. Here's why WooCommerce and other plugins still create accounts, and the one filter that guards admin creation.
WordPress ships with exactly one control against spam user registration, and it’s the first thing every guide on this topic tells you to flip: Settings → General → uncheck “Anyone can register” — officially labeled “Membership.” It takes ten seconds. If your site never needed public registration in the first place, that alone might be the end of the story. But if you’re here because your users list keeps growing with names you don’t recognize, turning that checkbox off is where most advice stops — and it’s the least important of three things that actually matter.
Turn off “Anyone can register” first
The checkbox lives on the General Settings screen and maps to a single option in the database, users_can_register. Unchecked, it does exactly what it says: WordPress’s native registration form stops working. Visit wp-login.php?action=register on a site with the box off and you get redirected back to the login screen instead of a signup form — the request that would have created an account never reaches register_new_user() at all.
That’s a real fix, and it’s also why so many “stop spam registrations” articles treat it as the whole answer. It closes exactly one door. users_can_register is checked by exactly one native code path — that form, and the login screen’s link to it. It has no authority over anything else on your site, in your plugins, or in the transports WordPress exposes, that decides to insert a user row.
Why the checkbox alone doesn’t stop spam user registration
You’ll still see wp-login.php?action=register show up constantly in your logs after flipping the setting off. Bots don’t check whether registration is open before they try — hitting that URL directly, with or without a payload, is cheap, and plenty of registration-spam bots are just running the same script against every WordPress site they can find, checkbox or no checkbox. It’s noise, not a breach, but it’s the reason people assume the checkbox alone isn’t working: the requests keep coming even though none of them succeed.
The paths that do still create accounts are the ones that never asked users_can_register in the first place. WooCommerce is the cleanest example: a guest checkout creates a customer account by calling wc_create_new_customer() directly, and that function has no idea the Membership setting exists — it’s not supposed to. Every membership plugin, forum plugin, and course platform on your site follows the same pattern: its own signup flow, its own account-creation function, none of it routed through the setting you just turned off. Add a custom REST endpoint — a lot of membership and ecommerce plugins ship one — and you’ve got several ways for a new wp_users row to appear that have nothing to do with the form you just locked down.
xmlrpc.php is a different kind of problem, worth naming separately so it doesn’t get conflated with the paths above: it doesn’t create accounts. It’s where bots go to brute-force the ones you already have, hammering system.multicall to test stolen or guessed credentials against your existing users in bulk. Real risk, wrong bucket — see should you disable XML-RPC in WordPress for whether shutting that endpoint off is the right call on your site.
Most of that traffic is exactly what it looks like: junk accounts, dead weight, an annoyance in your users list. Genuinely worth stopping, but not the part of this problem that can hurt you.
Honeypots and CAPTCHA solve a different problem
If you’ve added a honeypot field or a CAPTCHA to a registration or checkout form, it’s doing real work — it filters out the dumb, high-volume bots that fill in every field on a form and submit it, and it will visibly cut your junk-account count. What it can’t touch is anything that isn’t submitting your form. A WooCommerce checkout call, a membership plugin’s own endpoint, a script talking to a REST route directly — none of that renders your HTML, sees your honeypot field, or is asked to solve a CAPTCHA. Form-level spam defenses are worth having. They’re not a security boundary, and they were never going to be.
The risk that actually matters: a new administrator
Junk subscriber accounts are a cleanup task. There’s a different scenario worth taking seriously, and it doesn’t look like spam at all: a vulnerable plugin, a leaked admin session, or stolen credentials get used to create one new user, with the role set to administrator, and nothing else happens for weeks. No defaced homepage, no obvious break-in — just a second set of admin credentials sitting quietly in your users list, indistinguishable from any other account, ready to use whenever the attacker wants full control back. If a compromised plugin is the entry point — see our guide on blocking plugin installs for why every install is attack surface, or locking a plugin’s version for what happens when a previously-safe plugin changes hands — this is usually the next move, not the first one. It’s the post-compromise step that turns a one-time breach into standing access.
That’s the registration problem worth designing around: not the volume of junk accounts, but the one account that shouldn’t exist.
The one filter that can actually stop it
Every path to a new WordPress user — the native form, wc_create_new_customer(), a membership plugin’s signup handler, a custom REST endpoint, all of it — eventually calls the same core function, wp_insert_user(). And there’s exactly one filter capable of stopping that call before the row gets written: wp_pre_insert_user_data. Return a WP_Error from it and the insert aborts. No other hook in that chain can veto the creation itself.
That makes it the right place to guard specifically against a new administrator appearing, and the wrong place to try to block everything:
add_filter( 'wp_pre_insert_user_data', function ( $data, $update, $user_id, $userdata ) {
if ( ! $update && 'administrator' === ( $userdata['role'] ?? '' ) ) {
return new WP_Error( 'blocked', 'New administrator accounts are disabled here.' );
}
return $data;
}, 10, 4 );
That snippet only looks at brand-new users being created with the administrator role. Everything else — a WooCommerce customer, a membership plugin’s subscriber, a REST-created contributor — sails through untouched, because $userdata['role'] isn’t administrator for any of them.
Why blocking everything breaks checkout
It’s tempting to reach for the blunt version: hook the same filter and reject any programmatic user creation that didn’t come through the wp-admin dashboard. Don’t. WooCommerce checkout creates a customer account as part of a normal, wanted order. Every membership plugin creates a subscriber account as the entire point of signing up. Block all of it, and you haven’t stopped an attacker — you’ve broken the store and locked out every new member, the same day someone happens to try either one.
The right shape is narrower than that instinct, and it’s the one the code above already takes: guard administrator creation specifically, and leave customer and subscriber creation alone. That’s a deliberate trade — it means a compromised plugin could still, in theory, create a junk subscriber account — but it closes the one door that matters (a silent new admin) without touching the two flows (checkout, membership signup) that exist because you want them to.
The shortcut
Registration Lockdown is one of the six locks in WP Pro Admin, live now in the 2.x releases. It ships with the split above built in rather than left as a snippet you have to maintain:
The administrator-creation guard is on by default — it hooks wp_pre_insert_user_data the way the snippet above does, so a new administrator account can’t be created outside the dashboard, and nothing else changes. WooCommerce checkout and membership plugins keep working, because they were never creating administrators to begin with. Blocking all programmatic user creation is a separate toggle, off by default, for the sites that genuinely want nothing but dashboard-created accounts and are prepared for that to mean no checkout signups either.
Only the admin-guard hits get logged — not every blocked subscriber or every bot that hit the registration form — and that log is capped, so a script hammering the site doesn’t turn into a database write on every single attempt. You get a record of the one event that actually mattered, not a flood of the ones that didn’t.
It’s a free download, GPL, not yet on wordpress.org, and it will never show you an ad, an upgrade nag, or a promotional notice while you use it — a promise enforced by an automated test in the build, not a paragraph on a website.