Skip to main content

Cloudflare Turnstile (CAPTCHA) Configuration

Turnstile protects login, registration, forgot-password, reset-password, and contact form from bots. Configure it in your .env with keys from the Cloudflare dashboard.

Step 1: Create a Turnstile site in Cloudflare

  1. Log in to Cloudflare Dashboard.
  2. Select your account (or create one).
  3. In the left sidebar, open Turnstile (under "Web3" or search for "Turnstile").
  4. Click Add site.
  5. Set:
    • Site name — e.g. "Bellamy Book"
    • Domain — Add your frontend/admin/api domains (e.g. app.yourdomain.com, www.yourdomain.com if you use www redirect for SEO, admin.yourdomain.com, api.yourdomain.com). For local testing you can add localhost.
    • Widget type — "Managed" (recommended) or "Non-interactive" / "Invisible" if you prefer.
  6. Click Create and copy the Site Key and Secret Key.

Step 2: Set environment variables

In your .env:

VariableDescription
Turnstile__SiteKeyThe Site Key from Cloudflare (public; used by the frontend).
Turnstile__SecretKeyThe Secret Key from Cloudflare (private; used by the API only).
Turnstile__VerificationUrlLeave as https://challenges.cloudflare.com/turnstile/v0/siteverify unless Cloudflare changes it.
Turnstile__AllowedDomains__0, __1, …Hostnames where the widget is allowed. Usually match your Traefik hosts: ${TRAEFIK_FRONTEND_HOST}, ${TRAEFIK_ADMIN_HOST}, ${TRAEFIK_API_HOST}, ${TRAEFIK_DOCS_HOST}. If you use www redirect for SEO, add ${TRAEFIK_FRONTEND_WWW_HOST}.

Example:

Turnstile__SiteKey=0x4AAAAAAA...
Turnstile__SecretKey=0x4AAAAAAA...
Turnstile__VerificationUrl=https://challenges.cloudflare.com/turnstile/v0/siteverify
Turnstile__AllowedDomains__0=app.yourdomain.com
Turnstile__AllowedDomains__1=www.yourdomain.com
Turnstile__AllowedDomains__2=admin.yourdomain.com
Turnstile__AllowedDomains__3=api.yourdomain.com
Turnstile__AllowedDomains__4=docs.bellamybook.com

The backend already enables Turnstile for these endpoints: login, register, forgot-password, reset-password, contact. You can override with Turnstile__EnabledEndpoints__0, etc. if needed.

Step 3: Frontend and Admin (runtime – pre-built images)

With pre-built images, the frontend and admin get the Turnstile site key (and optional theme/size/endpoints) from runtime config — set these in your .env:

VariableDescription
VITE_TURNSTILE_SITE_KEYSame value as Turnstile__SiteKey (public site key).
VITE_TURNSTILE_THEMEOptional: light or dark.
VITE_TURNSTILE_SIZEOptional: normal or compact.
VITE_TURNSTILE_ENABLED_ENDPOINTSOptional: e.g. login,register,forgot-password,reset-password,contact.

The backend uses Turnstile__SiteKey and Turnstile__SecretKey; the frontend and admin use VITE_TURNSTILE_SITE_KEY. Set both so API and UI use the same site key.

Step 4: Restart

After changing .env:

docker compose restart api frontend admin

Troubleshooting

Production (e.g. local server + Cloudflare Tunnel) and self-hosted (e.g. Google Cloud VPS + Cloudflare Tunnel) both use Cloudflare Tunnel; only the origin server differs. If production works but a new self-hosted domain (e.g. bellbooktest.online) does not:

  1. Turnstile 110200 — In Cloudflare Dashboard → Turnstile → your site → Domains, add every hostname for the new site (e.g. admin.bellbooktest.online, app.bellbooktest.online). Production hostnames are usually already there; the new domain is not, so add it.
  2. CSP blocking scripts — Rebuild and redeploy the admin (and frontend) image from this repo so the relaxed CSP (allowing static.cloudflareinsights.com and cloudflareinsights.com) is in the build. Production may be running a build that already has it.

Disabling Turnstile

To disable Turnstile (e.g. for local dev), you can leave Turnstile__SecretKey and Turnstile__SiteKey empty or set a backend option to skip verification (if your app supports it). Check the API configuration for a "Turnstile disabled" or "skip verification" option.

Next steps