How to Stop Contact Form Spam on a WordPress Site (Without Blocking Real Visitors)
Your contact form fills your inbox with junk. Find how the spam arrives, then layer honeypots, Turnstile or CAPTCHA, Akismet, validation and rate limits.
Spam floods, CAPTCHA errors, forms that fail on submit, or entries with no email.
Do this first
Diagnose it
Form trouble splits two ways: forms that block real people, and forms that let junk through. Fixes for one often cause the other, because a harsher CAPTCHA stops customers too.
| What you see | What it usually means |
|---|---|
ERROR for site owner: Invalid domain for site key in the reCAPTCHA box | The key’s domain list does not include this hostname. Common after a domain change or a launch from staging. |
ERROR for site owner: Invalid key type | A key made for one reCAPTCHA type (such as v2 checkbox) is used where the plugin expects another (such as v3). |
Turnstile error 110200 | Domain not authorized. The hostname is missing from the widget’s Hostname Management list. |
| Real visitors told they look like spam or failed the CAPTCHA | A reCAPTCHA v3 score under the plugin’s threshold, an expired token, or a wrong secret key on the server. |
The link you followed has expired. | A WordPress nonce check failed: a cached page holding an old token, or an upload bigger than PHP accepts. |
Cookie check failed with code rest_cookie_invalid_nonce | A form posting to the REST API with a stale nonce, usually from a cached page. |
| A success message, but no stored entry and no email | The submission did not complete on the server, or the plugin does not store entries. |
| An entry stored, but no email | Mail delivery, not the form. See the email stopped working triage. |
403 Forbidden on submit | A firewall or ModSecurity rule matched the message. |
413 Request Entity Too Large, or The uploaded file exceeds the upload_max_filesize directive in php.ini. | The upload is larger than the web server or PHP allows. Nginx allows 1 MB unless configured otherwise. |
| Floods of junk entries, new subscriber accounts, or comments | Bots using an open form, registration or comments. |
1. Watch the submission in the browser. Open the form in a private window, press F12, open the Network tab, and submit. Find the POST request: to admin-ajax.php, a /wp-json/ address, or the page itself. Good: 200 and a success reply. Bad: 200 with an error in the reply body (the plugin rejected it; read the message), 403 (firewall or nonce), 413 (size), 500 (a PHP error). No request at all means a JavaScript error stopped the form; check the Console tab.
2. Find the CAPTCHA key the page uses.
curl -s https://example.com/FORM_PAGE/ | grep -oE '(render=|data-sitekey=.)[0-9A-Za-z_-]{20,}' | sort -u
Run it in your own computer’s terminal (on Windows, type curl.exe). Replace example.com with your domain and FORM_PAGE with the path of the page holding the form. It prints site keys from the reCAPTCHA script address or the widget’s data-sitekey. Good: one key, found in your reCAPTCHA or Turnstile account with this hostname on its domain list. Bad: two keys (two plugins each adding a CAPTCHA), or a key in no account you control.
3. Test that the secret key is accepted.
curl -s -d 'secret=SECRET_KEY&response=test' https://www.google.com/recaptcha/api/siteverify
Run it on your own computer. SECRET_KEY is the secret saved in your plugin’s settings. -d sends it as a POST with a fake token. Good: only invalid-input-response, meaning the secret works and the fake token was rejected. Bad: invalid-input-secret, meaning the secret is wrong, deleted or for another key. For Turnstile, send the same request to https://challenges.cloudflare.com/turnstile/v0/siteverify. Do not paste the secret anywhere public.
4. Check registration and comment exposure.
wp option get users_can_register
wp option get default_role
wp comment list --status=hold --format=count
Run these in an SSH terminal inside the site’s folder (the one with wp-config.php). Good: 0 unless you need public sign-ups, subscriber as the default role, and a small moderation queue. Bad: 1 on a site that never needed accounts, or a default role of administrator or editor, which is a sign of a compromise. Follow the hacked site steps if so.
5. Check the PHP upload limits.
wp eval 'echo ini_get("upload_max_filesize"), " / ", ini_get("post_max_size"), "\n";'
Run it in the same terminal. Good: both above the largest file your form accepts, post_max_size the larger. WP-CLI can read a different php.ini, so confirm under Tools, Site Health, Info, Server. A 413 is the web server’s limit, which your host sets.
invalid-input-secret, or forms fail with no change on your side. Google moved reCAPTCHA keys into Google Cloud projects; the free tier there covers 10,000 assessments a month, and use above that needs billing. Fix: find the key’s project, check its status and usage, and save fresh keys if needed.403 in check 1, triggered by certain words, links or code in the message. Fix: send the rule ID from the host’s log so they can adjust it.413 or the upload_max_filesize message. Fix: raise the PHP and web server limits and set the form’s maximum below them.Did this fix it?
Still stuck? Start again from what you can see in the Emergency Room.
The full fix
Your contact form fills your inbox with junk. Find how the spam arrives, then layer honeypots, Turnstile or CAPTCHA, Akismet, validation and rate limits.
Hundreds of fake subscribers or a comment queue full of junk? Close every registration door, bulk-remove fake users safely and lock down comments.
The form says “sent” or spins forever, but no entry or email ever shows up. Find out whether the submission or the notification failed, then fix that half.
WordPress says “The link you followed has expired” when you upload a theme, save a setting or submit a form. Find which nonce failed and why, then fix it.
Media uploads fail with an HTTP error, an image processing error or a file type refusal. Read the log, then fix memory, Imagick, file types or folder permissions.
Screen reader users cannot tell what a field wants, errors are invisible to them, or a CAPTCHA blocks them. Fix labels, errors, autocomplete, grouping and submit messages.
Dozens of failed orders with random names and declined cards mean someone is testing stolen cards on your checkout. Confirm it, slow it down today, clean up and harden the store.
A visitor, a service or you got a Cloudflare block page. Search the Ray ID in Security Events, see which rule fired, and narrow it or skip it instead of turning protection off.
You offer a free checklist or guide in exchange for an email address. Here is how to ask for consent honestly, confirm the address, deliver a link that arrives and keep the list healthy, whichever email service you use.
Search the field manual
Or jump straight to a symptom
Press / to search from any page, Esc to close