Turnstile foundations
Separate site and secret keys
Publish the site key in browser markup while keeping the secret key only in the server environment.
8 minute lesson
When you create a Turnstile widget in the Cloudflare dashboard, you get two keys with very different jobs.
The site key identifies the widget and is safe to send to browsers. It sits in your HTML where anyone can read it:
<div class="cf-turnstile" data-sitekey="0x4AAAAAAABkMYinukE8nzKd"></div>
The secret key authenticates Siteverify requests and must stay in a server secret store. On Workers or Pages, that means:
npx wrangler secret put TURNSTILE_SECRET_KEY
The word “key” hides the difference, so keep the rule mechanical: the site key ships to the client, the secret key never does. A leaked site key is harmless. A leaked secret key lets anyone forge “this is a human” verdicts against your backend.
One widget per environment
Use separate widgets for development, staging, and production. Restrict production hostnames in the widget settings and name widgets by purpose, something like contact-form-prod and signup-staging. For local work, configure Cloudflare’s documented test keys instead of a real widget, so your development traffic never touches production analytics.
Verify nothing leaked
Frameworks make leaking easy. In Astro and Vite projects, any environment variable prefixed with PUBLIC_ gets inlined into the client bundle. Name the secret TURNSTILE_SECRET_KEY, not PUBLIC_ANYTHING, and read it only in server code.
After a build, check the output:
npm run build
grep -r "0x4AAAAAAABkMY_your_real_secret" dist/ && echo "LEAKED" || echo "clean"
Confirm the production secret never appears in generated HTML or browser network responses. Open devtools on the deployed form and search the page source and responses for the secret’s first characters.
If a secret appears in source, logs, or client JavaScript, rotate it and investigate every environment that used it. Rotation is cheap. An attacker quietly bypassing your bot protection for months is not.
Lesson completed