Cloudflare Turnstile Not Working in WordPress? Here's the Fix
You swapped out reCAPTCHA for Cloudflare Turnstile because it's faster, free, and doesn't track your visitors — and now the widget won't load, or it loads but every form submission comes back "verification failed." Here's how to actually fix it, not just refresh the page and hope.
Why site owners are moving to Turnstile
Google reCAPTCHA works, but it ships third-party tracking cookies, occasionally shows an infinite loop of traffic-light photos to real customers, and adds a noticeable delay before your contact form is usable. Cloudflare Turnstile solves the same problem — tell bots from humans — without the cookie banner headache or the "select all the crosswalks" tax on your visitors. Most of the time it runs an invisible, non-interactive check and just passes.
The catch: because it verifies the visitor's browser and the exact domain it's embedded on, a handful of setup details will silently break it. Below are the ones we see most often on customer sites.
Symptom 1: The widget never appears
You add the shortcode or plugin block, save the page, and where the checkbox or Cloudflare badge should be, there's just... nothing. No error in the form, no console warning you'd notice at a glance.
Cause
- The Turnstile JavaScript (
https://challenges.cloudflare.com/turnstile/v0/api.js) is being blocked by a caching or "defer non-essential JS" plugin like WP Rocket, LiteSpeed Cache, or Autoptimize. - A Content Security Policy header (set in
.htaccessor a security plugin) doesn't allow scripts fromchallenges.cloudflare.com. - The site key was pasted with a stray space or line break — this is the single most common cause we see in support tickets.
Fix
- Open your browser's DevTools console (F12) on the page with the form. If you see a CSP violation error naming
challenges.cloudflare.com, that confirms cause #2. - If you're using a CSP header, add this to your
script-srcandframe-srcdirectives:script-src 'self' https://challenges.cloudflare.com; frame-src 'self' https://challenges.cloudflare.com; - Exclude the Turnstile script from JS defer/delay optimisation. In WP Rocket: Settings → File Optimization → Excluded JavaScript Files, add
challenges.cloudflare.com. In LiteSpeed Cache: Page Optimization → JS Settings → JS Excludes. - Re-copy your site key from the Cloudflare dashboard (Turnstile → your widget → Sitekey) and paste it into a plain-text editor first to strip formatting, then into your plugin settings.
Symptom 2: Widget shows, but every submission fails with "verification failed"
The checkbox or badge renders fine. The visitor completes it (or it auto-passes). They click submit, and the form bounces back with a generic error, or the WordPress error log shows something like cf-turnstile-response missing or invalid-input-response.
Cause
This is almost always a mismatch between the site key (public, goes in your HTML) and the secret key (private, used server-side to verify the token with Cloudflare's API). Common ways this happens:
- You pasted the secret key into the site key field, or vice versa — they look similar and it's an easy copy-paste mistake.
- You have a staging and a production Turnstile widget, and the keys got swapped during deployment.
- Your server can't reach Cloudflare's verification endpoint (
https://challenges.cloudflare.com/turnstile/v0/siteverify) — outbound HTTPS is blocked by a firewall rule, mod_security, or an aggressive VPS security group. - A caching plugin is caching the form page including the one-time Turnstile token, so the visitor submits a token that already expired (tokens are valid for roughly 5 minutes and are single-use).
Fix
- In the Cloudflare dashboard, go to Turnstile and confirm which key is which — the sitekey is the one you never treat as secret. Re-check both fields in your plugin.
- Test outbound connectivity from your server directly:
If this times out or connection-refuses, your hosting firewall (CSF, ModSecurity, or a VPS security group) is blocking outbound HTTPS to that host. On a cPanel/CSF server, whitelist the outbound connection incurl -I https://challenges.cloudflare.com/turnstile/v0/siteverify/etc/csf/csf.confor ask support to confirm outbound 443 isn't restricted. - Exclude the page containing your form from full-page caching, or at minimum exclude any URL parameter/cookie the form plugin sets. Most contact-form plugins (Contact Form 7, WPForms, Gravity Forms) already send no-cache headers on their own AJAX endpoint — check that your caching plugin isn't overriding that.
- If you run WooCommerce checkout or a login form behind Turnstile, make sure the widget re-renders after AJAX page updates (cart totals refresh, etc.) — a stale token from before the AJAX reload will always fail.
Symptom 3: Works on staging, fails on the live domain
You tested everything on staging.yoursite.com, it passed every time. You push to production and it breaks immediately.
Cause
Turnstile widgets are locked to the exact hostnames you list when you create them in the Cloudflare dashboard. If your production domain, its www variant, or your staging subdomain isn't in that list, verification fails with a domain mismatch — Cloudflare's API rejects it silently from the form's point of view.
Fix
In Cloudflare → Turnstile → your widget → Settings, check the "Domains" list. Add every hostname the form is actually served from:
| Hostname you need to add | Why |
|---|---|
| yourdomain.com | Root domain |
| www.yourdomain.com | www redirect target, if visitors land here first |
| staging.yourdomain.com | Staging/testing environment |
| localhost / 127.0.0.1 | Local development — Turnstile has a dedicated toggle for this |
Changes to the domain list apply within a minute or two — no need to regenerate the keys.
Symptom 4: Turnstile passes, but the form still submits nothing (WPForms/CF7)
This one isn't a Turnstile problem at all — it's a plugin integration gap. Some older versions of popular form plugins added Turnstile support after reCAPTCHA, and the field name they expect doesn't match what a hand-rolled integration sends.
Fix
- Update the form plugin to the latest version — Turnstile support in Contact Form 7, WPForms, and Gravity Forms has matured a lot since first release and early versions had known bugs here.
- If you added Turnstile manually via a snippet, confirm the hidden field name is exactly
cf-turnstile-response— this is what the server-side verification call reads. - Check
wp-content/debug.log(withWP_DEBUG_LOGenabled) for a PHP notice about an undefined index — that usually points straight at the mismatched field name.
Prevention checklist
- Keep a copy of your site key and secret key in a password manager, labelled clearly — don't rely on remembering which is which.
- Add every environment's hostname to the widget's domain list before you deploy, not after something breaks.
- Exclude the Turnstile script and your form page from aggressive JS-delay and full-page caching from day one.
- After any migration (new server, new domain, cPanel-to-cPanel move), re-verify the domain list and re-test the form once DNS has cut over.
- If you self-host behind a VPS firewall, confirm outbound HTTPS to
challenges.cloudflare.comis allowed as part of your first-hour server checklist.
Frequently asked questions
Is Cloudflare Turnstile free to use?
Yes. Turnstile is free for unlimited sites and requests — you don't need to be on Cloudflare's paid plans or even proxy your DNS through Cloudflare to use it. You only need a free Cloudflare account to generate a site key and secret key.
Do I need my domain's nameservers on Cloudflare to use Turnstile?
No. Turnstile is a standalone product — it works on any domain, on any host, regardless of who manages your DNS. Plenty of Getwebup customers run Turnstile on cPanel-hosted WordPress sites with nameservers pointed elsewhere.
Can I run Turnstile and reCAPTCHA on the same site during migration?
Most plugins let you configure only one CAPTCHA provider per form at a time, but you can run Turnstile on some forms and reCAPTCHA on others while you migrate. Just don't leave both scripts loading on the same form — they can conflict over the same submit button hook.
Why does the Turnstile checkbox stay stuck on 'verifying'?
This almost always means the browser can't reach challenges.cloudflare.com — check for an ad blocker or privacy extension blocking the request, a corporate firewall, or (server-side) an expired SSL certificate on your own domain that's breaking the page's mixed-content rules.
Does Turnstile slow down my page like reCAPTCHA sometimes does?
It's noticeably lighter — Turnstile's managed challenge is designed to run invisibly in most cases and only escalates to an interactive check for suspicious traffic, so the vast majority of real visitors never see more than a brief badge.