Skip to content
  • FG-73
  • 12 min read
  • Last checked November 8, 2026

How to Stop Fake User Registrations and Comment Spam in WordPress

Hundreds of fake subscribers or a comment queue full of junk? Close every registration door, bulk-remove fake users safely and lock down comments.

Run the health check

Your inbox fills with “New User Registration” notices. The Users screen shows hundreds of subscribers with random names like jamesmith8241 and email addresses at domains you have never heard of. Or the Comments screen has thousands of pending items full of links, and real comments are lost among them.

This is automated abuse of doors that WordPress leaves open when certain settings are on. It is usually not a hack. But one setting, the default role for new users, turns it into a serious security problem if it is wrong. This guide shows how to close each door, remove fake accounts safely in bulk, and tame comments. If the trouble is junk arriving through a contact form, the contact form spam guide covers that instead.

What is happening

WordPress, WooCommerce and multisite each have their own sign-up URL. Bots do not use your menus; they request these URLs directly and post the form. Comment spam works the same way: bots post straight to wp-comments-post.php, and pingback spam arrives through XML-RPC.

Door URL bots post to Setting that controls it
Core registration /wp-login.php?action=register Settings, General, Membership: “Anyone can register” (option users_can_register)
WooCommerce account /my-account/ registration form WooCommerce, Settings, Accounts & Privacy, Account creation: “On “My account” page”
Multisite sign-up /wp-signup.php Network Admin, Settings, Network Settings: “Allow new registrations”
Membership or form plugin The plugin’s own form or endpoint That plugin’s settings
Comments /wp-comments-post.php Settings, Discussion, plus each post’s Discussion panel
Pingbacks and trackbacks /xmlrpc.php, /wp-trackback.php Settings, Discussion: “Allow link notifications from other blogs”

Why do bots want accounts? Some profiles carry a website link. Some spam tools comment as logged-in users to skip moderation. And a subscriber account is the starting point for attacks on plugins that fail to check permissions properly. Every fake account is a small, real risk, which is why removing them matters.

Before you start

You need an administrator login. For bulk work you want SSH and WP-CLI, because deleting thousands of users through the browser times out.

Take a database backup first. Deleting users is permanent. Run wp db export from the site folder and keep the file somewhere safe.

How to run commands: connect with SSH and change into the site folder, the one containing wp-config.php. Your host may name the tool wp or wp-cli. The SQL examples use PREFIX_ for your table prefix; find yours with wp config get table_prefix (the default is wp_). On multisite, add --url=https://SITE_DOMAIN/ to target one site. The diagnostic tools help with checks you can do from a browser.

Step 1: Check the registration settings and the default role

Run this on the server:

wp option get users_can_register
wp option get default_role

There are no placeholders. wp option get prints the stored value of an option. Good output is 0 for the first line (registration off) and subscriber or customer for the second. A 1 means anyone can register. A default role of administrator, editor or author is bad: every bot account gets that power.

A default role of administrator that you did not set is a sign of compromise, not a misconfiguration. Attackers flip it so they can register their own admin. If you see it, stop and follow the guide on the first hour after a hack before cleaning up users. WordPress 7.0 and later hide Administrator and Editor from the New User Default Role dropdown, but the option can still be changed directly in the database, so the command above is the reliable check.

Turn registration off

If no feature on your site needs public accounts, uncheck “Anyone can register” in Settings, General and save, or run:

wp option update users_can_register 0
wp option update default_role subscriber

wp option update writes the value. Good output is Success: Updated 'users_can_register' option. If it says the value is unchanged, it was already set.

If you do need public registration, for example for a course or a forum, keep it on but add Turnstile or a CAPTCHA to the registration form through your security or membership plugin, and make sure the challenge is verified on the server.

Step 2: Close the other registration doors

WooCommerce “My account” registration

Go to WooCommerce, Settings, Accounts & Privacy. Under Account creation you will see “After checkout”, “During checkout” and “On “My account” page”. The last one shows a registration form to anyone, with no purchase needed, and it is the usual source of fake customers. Uncheck it unless you need it. Customers can still create accounts while buying if you keep the checkout options. On the server:

wp option get woocommerce_enable_myaccount_registration

Good output is no. yes means the My account page accepts sign-ups. WooCommerce ignores the core “Anyone can register” setting for its own form, which is why fake customers can appear even after Step 1.

Multisite and wp-signup.php

On a network, the Membership checkbox is replaced by Network Admin, Settings, Network Settings, “Allow new registrations”. The choices are “Registration is disabled”, “User accounts may be registered”, “Logged in users may register new sites” and “Both sites and user accounts can be registered”. Check it on the server:

wp site option get registration

Good output is none. user, blog or all mean wp-signup.php accepts sign-ups. Fake site registrations on a network are worse than fake users, because each one creates a new site with its own tables.

On a single site, wp-signup.php just redirects to the normal registration URL. Bots still request it, so you will see it in logs; that is harmless once registration is off.

Membership and form plugins

Plugins such as membership, LMS and form builders with user registration add their own sign-up forms that may not respect the core setting. Check each one, and search your pages for registration shortcodes or blocks you forgot about.

Step 3: Find the fake users without deleting anything

Always look before you delete. Start with counts, run on the server:

wp user list --role=subscriber --format=count
wp user list --role=subscriber --fields=ID,user_login,user_email,user_registered --orderby=registered --order=DESC | head -40

--role limits the list to one role. --format=count prints only the total. --fields picks columns. --orderby and --order are passed to WordPress user query, so the newest accounts come first. head -40 shows the first 40 lines. Good output is a small, recognizable list. Bad output is a long run of recent sign-ups with patterned usernames and throwaway domains, often in bursts at the same minute.

Filter by email domain

wp user list --search="*@SPAM_DOMAIN" --fields=ID,user_login,user_email,user_registered

SPAM_DOMAIN is a domain that repeats in the fake accounts. The leading asterisk is a wildcard, so the search matches addresses ending in that domain. Good output lists only junk; if a real person appears, refine the pattern.

Find subscribers who never did anything

Fake accounts almost never write posts or comments. This query lists subscribers registered since a date who have neither:

wp db query "SELECT u.ID, u.user_login, u.user_email, u.user_registered FROM PREFIX_users u JOIN PREFIX_usermeta m ON m.user_id = u.ID AND m.meta_key = 'PREFIX_capabilities' WHERE m.meta_value LIKE '%subscriber%' AND u.user_registered >= 'YYYY-MM-DD' AND NOT EXISTS (SELECT 1 FROM PREFIX_posts p WHERE p.post_author = u.ID) AND NOT EXISTS (SELECT 1 FROM PREFIX_comments c WHERE c.user_id = u.ID)"

PREFIX_ is your table prefix in all five places. YYYY-MM-DD is the date the spam began, in UTC. The PREFIX_capabilities meta row stores each user’s role. Good output is a table of rows you are happy to delete. Bad output includes names you know, such as newsletter subscribers or members; tighten the date or add AND u.user_email NOT LIKE '%@YOUR_DOMAIN' to protect staff.

On WooCommerce, never bulk delete the customer role without checking orders first. A customer with no posts or comments may still have orders, and deleting the account detaches their order history from it.

Step 4: Delete fake users in safe batches

Save the IDs to a file you can review, then delete in batches. On the server:

wp db query "SELECT u.ID FROM ...same query as above..." --skip-column-names > fake-user-ids.txt
wc -l fake-user-ids.txt
wp user delete $(head -n 100 fake-user-ids.txt) --reassign=USER_ID

Replace the middle of the first line with the full query from Step 3, changing the selected columns to just u.ID. --skip-column-names is passed to MySQL so the file holds bare IDs. wc -l counts lines, so you can compare it with what you expected. USER_ID is the numeric ID of your own administrator account; find it with wp user get YOUR_LOGIN --field=ID. --reassign moves any content the deleted user owned to that account instead of deleting it. Without it, WP-CLI asks whether to delete the content too. head -n 100 takes 100 IDs at a time, which avoids the “argument list too long” error.

Good output is one Success: Removed user USER_NUMBER from https://example.com. line per account. Remove the first 100 lines from the file with sed -i '1,100d' fake-user-ids.txt and repeat until it is empty. On multisite, plain wp user delete only removes the user from the current site; add --network to remove them from the whole network.

Step 5: Set up comment moderation properly

Go to Settings, Discussion. These are the settings that matter for spam, with their stored option names and defaults:

Label Option Default Suggested
Allow people to submit comments on new posts default_comment_status open Off if you do not want comments
Users must be registered and logged in to comment comment_registration off Rarely helpful if registration is open
Automatically close comments on old posts close_comments_for_old_posts / close_comments_days_old off / 14 days On, 30 to 90 days
Comment must be manually approved comment_moderation off On for low-traffic sites
Comment author must have a previously approved comment comment_previously_approved on Keep on
Number of links before holding for moderation comment_max_links 2 1
Comment Moderation word list moderation_keys empty Add repeated spam words
Disallowed Comment Keys disallowed_keys empty Use sparingly; matches inside words

Both word lists match inside words, so a short entry such as “sex” will also catch “Essex”. Disallowed keys send matching comments to the Trash without telling you, so a bad entry silently loses real comments.

The auto-close setting is applied when a page loads, so it closes comments on existing old posts too, not only new ones. By default it applies to posts, not pages.

Add Akismet

Akismet checks each comment against its network-wide spam history and sends obvious spam to the spam folder. Install and connect it, then watch the Spam tab for a week for false positives. Its free Personal plan is for non-commercial sites only; business sites need a paid plan.

Empty the existing queue

wp comment list --status=spam --format=count
wp comment list --status=hold --format=count
wp comment delete $(wp comment list --status=spam --format=ids) --force

--status=spam selects comments marked spam; hold selects pending ones. --format=ids prints IDs on one line for the delete. --force deletes permanently instead of moving to Trash. Check the counts first. Do not bulk delete the pending queue without skimming it; real comments wait there too. With tens of thousands of IDs, the shell may report “argument list too long”; add --number=500 to the inner list command and repeat.

Step 6: Close comments on old content

If you want to close comments on everything published before a date, preview first:

wp db query "SELECT post_type, COUNT(*) FROM PREFIX_posts WHERE comment_status = 'open' AND post_status = 'publish' AND post_date < 'YYYY-MM-DD' GROUP BY post_type"

YYYY-MM-DD is the cut-off date. Good output is a short table of post types and counts that matches what you expect. Then run the change:

wp db query "UPDATE PREFIX_posts SET comment_status = 'closed', ping_status = 'closed' WHERE comment_status = 'open' AND post_status = 'publish' AND post_date < 'YYYY-MM-DD'"

The WHERE clause must match the preview exactly. No output means success; run the preview again and it should now return no rows. Existing comments stay visible; only new ones are refused. If you use a persistent object cache, run wp cache flush afterward so pages see the change.

Step 7: Disable comments sitewide

Many business sites never wanted comments. To shut them off completely:

  1. In Settings, Discussion, uncheck “Allow people to submit comments on new posts” and “Allow link notifications from other blogs”.
  2. Close existing posts with Step 6, using a cut-off date in the future so every post matches.
  3. Delete the spam queue with Step 5.
  4. Optionally use a plugin such as Disable Comments to hide the comment menus, widgets and admin bar items.

Bots will still post to wp-comments-post.php, and WordPress answers each one with “Sorry, comments are closed for this item.” That still costs a PHP request. If no part of your site uses comments, block the file at the web server. For Nginx, add this inside the server block of your site config:

location = /wp-comments-post.php { deny all; }

For Apache, add this to .htaccess in the site folder, outside the # BEGIN WordPress block:

<Files "wp-comments-post.php">
    Require all denied
</Files>

There are no placeholders. Both return 403 to anything posting a comment. Confirm from your computer with curl -I -X POST https://example.com/wp-comments-post.php, replacing example.com with your domain: good output starts with HTTP/2 403 (or HTTP/1.1 403). WooCommerce product reviews are posted through the same file, so do not block it if you collect reviews.

Advanced: confirm nothing worse happened

Fake accounts are noise, but an attacker can hide a real admin account among them. Check these on the server:

wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
wp user list --role=editor --fields=ID,user_login,user_email,user_registered

Run the second line again with --role=author. Good output contains only people you know. An administrator you did not create, especially one registered during the spam wave, means the site is compromised; go to the hack recovery guide and do not simply delete the account, because you need to find how it was created.

To see how registrations arrive, search the access log on the server:

grep -E "POST /(wp-login\.php\?action=register|my-account/|wp-signup\.php)" ACCESS_LOG | awk '{print $1}' | sort | uniq -c | sort -rn | head

ACCESS_LOG is your web server access log path, from your hosting panel or /var/log/nginx/access.log or /var/log/apache2/access.log. -E enables the pattern with alternatives, and the backslashes make the dots and question mark literal. The rest counts requests by client IP. Lines still appearing after you closed registration are failed attempts and can be rate limited at Cloudflare or the server, as described in the contact form spam guide.

When to contact your host

Open a ticket if you found an unknown administrator, if the users table is so large that WP-CLI runs out of memory, or if registration attempts are slowing the site. Send:

  • The site URL and whether it is multisite.
  • The user counts from Step 3 and the date range of the fake sign-ups, in UTC.
  • Access log lines showing the registration or comment POSTs.
  • The current values of users_can_register and default_role, and whether you changed them.
  • Any unknown admin usernames and their registration times.

If the site is down or defaced, go to the Emergency Room first.

Still stuck?

Print a free one-page checklist to keep, or open the Emergency Room if the symptom does not match.

Get the free checklists

Keep reading

All field guides

Search the field manual

What is going wrong?

Press / to search from any page, Esc to close