CloudflareのTurnstileの使い方|失敗を防ぐ

CloudflareのTurnstileの使い方|失敗を防ぐ

こんにちは。Osukeラーニング、運営者の「osuke」です。

問い合わせformへ営業spamが増え、「画像を何枚も選ばせるCAPTCHAは利用者を離脱させそう」「Cloudflare Turnstileなら無料で簡単らしいが、widgetを置くだけでよいのか」と迷っていませんか。Turnstileは導入しやすい一方、client側の表示だけで送信を許可すると、攻撃者はform endpointへ直接requestを送り、検証を迂回できます。

結論からいうと、初めてならManaged modeでwidgetを作り、production hostnameを限定し、formへ埋め込み、backendからSiteverifyを呼ぶところまでを1セットにします。さらにrate limit、honeypot、入力validationを重ね、失敗時の再試行と問い合わせ代替手段を用意すれば、spamを減らしながら本物の利用者を置き去りにしにくくなります。

この記事はosuke本人の導入体験としては書かず、2026年8月29日時点のCloudflare公式資料12件と、Xで公開された8件の導入・移行・spam減少・無限loop・端末負荷事例を照合しました。Xの結果は各投稿者の環境に限られるため、仕様は公式資料を優先します。Cloudflare全体が初めてならCloudflareの無料範囲と機能の全体像から確認してください。

記事のポイント

  • 最初のwidgetはCloudflare推奨のManaged modeを選ぶ
  • sitekeyは公開用、secret keyはbackendだけで保管する
  • tokenは5分・1回限りなのでSiteverifyを送信直後に実行する
  • spam対策はrate limitやhoneypotと組み合わせて完成させる

Cloudflare Turnstileの仕組みと導入前の判断

Turnstileの設定画面を開く前に、何を防げて何が残るのかを分けます。無料という理由だけで導入すると、backend検証の漏れや本物の利用者が送信できない問題を見逃すため、mode、上限、重ねる対策、失敗時の逃げ道を先に決めます。

TurnstileはCAPTCHA操作を減らすbot対策

Cloudflare公式のTurnstile概要では、visitorのbrowser環境へ小さな非対話challengeを実行し、人間か自動trafficかを判定すると説明しています。画像の信号機や横断歩道を何度も選ばせる方式ではなく、Managed modeでは追加確認が必要なvisitorだけcheckboxを操作するため、form送信前の負担を抑えやすい設計です。

TurnstileはCloudflare CDNを通していないsiteにも埋め込めます。つまり、hosting先が別会社でも、Cloudflare accountでwidgetを作り、許可hostnameとkeyを設定すれば利用できます。XではCHiSA氏がreCAPTCHAからCloudflare TurnstileとContact Form 7の構成へ変更したと投稿しており、既存WordPress formの移行先として使われている具体例です。

効果の公開例として、otsune氏はreCAPTCHA時代に毎日のように届いた営業メールがTurnstile変更後に激減したと投稿しています。ただし手動の問い合わせは残ったとも述べています。Turnstileが判定するのは自動送信の疑いであり、商品を売り込みたい人が手で送る内容や、正規browserを操作する高度なautomationを必ず止めるものではありません。

Turnstileが向く場面
  • 問い合わせ・資料請求・予約form
  • account作成・login・password reset
  • comment・review・waitlist登録
  • checkoutやcoupon取得前のbot対策

無料枠は20 widgetsと無制限challenge

Cloudflare公式のTurnstile plansによると、Free planは月額0で、1 accountにつき最大20 widgets、challenge回数は無制限です。個人blog、小規模事業、開発・test環境だけでなく、多くのproduction applicationにもFreeが想定されています。request数が増えた瞬間に従量課金される料金表ではありません。

無料でも制約はあります。1 widgetへ登録できるhostnameは最大10件、Analyticsのlookbackは最大7日です。Enterpriseはwidgets無制限、1 widget最大200 hostnames、Analytics最大30日などへ広がります。20 widgetsを「20 pages」と誤解しないでください。通常は「contact form用」「login用」のように目的単位でwidgetを分け、同じwidgetを許可hostname内の複数pageで使えます。

公式Hostname managementでは、example.comを登録するとそのsubdomainも許可されます。一方、URL scheme、port、path、wildcardはhostname欄へ入れられません。productionのroot domainだけで足りるなら広げすぎず、stagingやlocalhostはtest keyへ分離すると、流出したsitekeyの無断利用と設定事故を減らせます。

項目Free実務での判断
料金無料challenge数による従量課金なし
Widgets最大20form用途・環境ごとに分ける
Hostnames1 widget最大10productionだけを許可する
Analytics最大7日週1回以上確認・必要なら外部log保存
無料枠で先に決めること
  • widgetをform用途別に分ける
  • productionとtestのkeyを混ぜない
  • hostnameは実際に使うdomainだけ許可する
  • 7日を超える傾向は自社logでも残す

Managed・Non-interactive・Invisibleを選ぶ

公式Widget modesにはManaged、Non-interactive、Invisibleの3種類があります。初回はCloudflareが推奨するManagedを選ぶのが安全です。visitorのriskに応じて非対話処理とcheckboxを切り替えるため、「誰にも操作させない」ことより、誤判定時に本人が確認できる余地を残せます。

Non-interactiveはspinnerを表示しますがvisitorへ操作を求めず、Invisibleはwidget自体を見せません。見た目だけならInvisibleが魅力的でも、処理中か失敗かを利用者が判断しにくくなります。決済、password reset、自治体formのように送信失敗のcostが大きい場面では、Managedまたは見える状態表示にして、error messageと再試行buttonを用意した方が運用しやすくなります。

公式Widget configurationsでは、通常300×65px、幅へ追従するflexible、150×140pxのcompactを選べます。appearanceは常時表示、challenge開始時、interactionが必要な時だけの3方式です。mobileのform幅が300px未満ならoverflowし得るため、公開前に実機幅、dark mode、browser zoom 200%でも送信buttonと重ならないか確認します。

ModeVisitorに見えるもの向く場面
Managed必要時のみcheckbox初回導入・一般form
Non-interactive処理中spinner操作を求めず状態は見せたい
Invisible原則何も表示しない独自の状態・error UIを作れるapplication
Mode選定の初期値
  • 迷ったらManaged
  • 処理中表示が必要ならNon-interactive
  • Invisibleはerror UIを自作できる場合だけ
  • mobileはflexibleまたはcompactを実機確認する

Turnstile単体ではspam対策は完成しない

Turnstileが成功したrequestをすべて安全とみなすと、別の穴が残ります。人間が送る営業、1人が何百回も繰り返すrequest、使い捨てemail、同じreferralの自己招待、payloadを巨大化した攻撃は、それぞれ判定軸が違います。Turnstileは「browserが人間らしいか」を見る一層であり、business ruleのvalidationまでは代行しません。

Xでは、Vlad Gerasimchuk氏が公開waitlistへ6種類のanti-abuseを実装し、per-IP rate limit、disposable-email block、honeypot、self-referral block、Turnstile、referral capを組み合わせたと投稿しています。目的ごとに防御を分ける考え方は、Turnstileを突破した1 requestがそのまま無制限処理へ進む事故を防ぎます。

問い合わせformなら、IPまたはaccount単位の送信回数、同一本文の連投、URL数、禁止文字数、存在しないhidden fieldへの入力を記録します。loginならTurnstileに加えてpassword試行回数と一時lockが必要です。Cloudflare側のWAFやrate limitingも含めた入口対策は、Cloudflareのセキュリティ設定で全体像を確認できます。

Formを守る4層
  • Turnstileでbrowser signalを検査
  • rate limitで短時間の連投を制限
  • server validationで入力値とbusiness ruleを確認
  • logと通知で突破・誤検知を発見する

誤検知とbrowser負荷へ逃げ道を用意する

摩擦が小さいTurnstileでも、すべてのvisitorが同じ速さで通過するわけではありません。古いbrowser、privacy extension、社内network、端末時計のずれ、途中cache、challenges.cloudflare.comの遮断でiframeやchallengeが失敗します。重要formで送信buttonを無効のままにすると、本物の問い合わせをspamと同時に失います。

Xでは希氏がFirefox更新後にTurnstileの無限loopが解消したと投稿しています。また、Bradley Erickson氏は直近の個別例でbrowser processが812MBのRAMを使ったと指摘しました。812MBは一般的な消費量ではなく本人環境の観測ですが、proof-of-workが端末resourceを使う以上、低性能端末やloopを無視しない材料になります。

CivicTechAkitaは自治体・地域DXで住民へstressを与えにくい防御策としてTurnstileを紹介しています。公共性や成約価値が高いformでは、error callbackへ「browser更新・extension一時停止・別browser」の案内を出し、電話やemailなど代替経路も残します。利用者を責める文言ではなく、「認証を再読み込みできませんでした」と次の行動を示します。

本物の利用者を失わない設計
  • 失敗理由を人間向けの文で表示する
  • widget resetと再試行buttonを置く
  • browser更新・extension・時計・networkを案内する
  • 高価値formは電話やemailの代替経路を残す

Cloudflare Turnstileの使い方と安全な実装

ここからはwidget作成、client埋め込み、server検証、test、監視の順に進めます。sitekeyを表示できただけでは完成ではありません。攻撃者がform画面を通らずendpointへ直接送る前提で、backendの許可条件を最後まで作ります。

Dashboardでwidgetと2種類のkeyを作る

公式Dashboard手順に沿い、Cloudflare DashboardのTurnstileから「Add widget」を選びます。Widget nameは「production-contact」「production-login」のように目的と環境が分かる名前にし、実際に表示するhostnameを登録し、最初はManaged modeを選んで作成します。CloudflareへdomainのDNSを移していなくても利用できます。

作成するとsitekeyとsecret keyが発行されます。sitekeyはHTMLへ置く公開識別子で、browserから見えて問題ありません。secret keyはSiteverifyを呼ぶbackend用で、HTML、client JavaScript、Git repository、screenshotへ入れてはいけません。WorkersならSecret、hosting serviceならenvironment variableへ保存し、権限を持つ担当者も最小限にします。

WP Ultimate Securityはsite keyとsecret keyを設定し、login、registration、password reset、checkoutを個別に有効化する流れを紹介しています。製品投稿なので効果保証には使いませんが、WordPressでは問い合わせformだけでなく複数の入口がある点は重要です。保護対象と未対応formを一覧にして、設定漏れを送信testで見つけます。

Widget作成時の記録
  • 名前にproduction・stagingと用途を入れる
  • 許可hostnameを画面保存する
  • sitekeyはclient、secretはbackendへ分離する
  • secretをrepositoryや記事へ貼らない

静的formとSPAで埋め込み方法を分ける

公式Client-side renderingでは、page読込時から存在する静的formにはimplicit rendering、modalやSPAで後から作るformにはexplicit renderingを案内しています。静的formはAPI scriptとcf-turnstile classのcontainerを置くだけで始められ、form内ならcf-turnstile-responseというhidden inputが自動生成されます。

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<form method="post" action="/contact">
  <!-- name・email・message fields -->
  <div class="cf-turnstile" data-sitekey="YOUR_PUBLIC_SITEKEY"></div>
  <button type="submit">送信</button>
</form>

SPAでroute遷移やmodal表示のたびにformを作る場合は、explicit renderingでcontainer生成後にturnstile.render()を呼び、form破棄時にwidgetも整理します。同じcontainerへ二重renderすると送信不能や古いtokenの原因になります。success、error、expired、timeoutのcallbackを用意し、送信中、再試行、失効をUIへ反映してください。

Content Security Policyを使うsiteでは、Turnstileがhostされるchallenges.cloudflare.comへのscript・frame・connectを許可する必要があります。Xではishan氏がlogin状態を再利用するbrowser agentでも厳しいsiteではTurnstile challengeが出ると投稿しており、cookieだけではなくbrowser挙動を含む判定が利用者体験へ影響する例です。自動E2E testはproduction widgetで判定させず、後述のdummy keyを使います。

Client実装の選択
  • 静的HTML formはimplicit
  • SPA・modal・動的formはexplicit
  • successだけでなくerror・expired・timeoutも処理
  • CSPでchallenges.cloudflare.comを遮断しない

BackendからSiteverifyを必ず実行する

公式Server-side validationは、Siteverify APIの呼び出しを必須と明記しています。Client widgetはtokenを発行するだけで、攻撃者は画面を省略してform endpointへ任意文字列を送れます。Backendは受け取ったcf-turnstile-responseを、secret keyと一緒にCloudflareへPOSTし、success:trueを確認してから本処理を実行します。

POST https://challenges.cloudflare.com/turnstile/v0/siteverify
Content-Type: application/json

{
  "secret": "SERVER_ENV_TURNSTILE_SECRET",
  "response": "TOKEN_FROM_FORM"
}

Tokenは発行から300秒、最大2048文字、1回限りです。送信を二重clickしたり、同じtokenをretryへ使ったりするとtimeout-or-duplicateになります。まずtokenの存在と長さを確認し、Siteverifyへ短いtimeoutを設定し、Cloudflare側の一時errorとinvalid tokenを分けます。通信失敗時に無条件で許可するfail-openはspam入口になるため、重要処理は再試行を促すfail-closedが基本です。

successだけでなくresponseのhostnameが自siteか、widgetへ設定したactionがcontactやloginなど期待値と一致するかも検査します。検証後にDB保存、email送信、account作成へ進めば、無効requestで外部API costを発生させません。Cloudflare Workersでbackendを作る場合は、Cloudflare Workersの始め方も参照してください。

Siteverifyの許可条件
  • tokenが存在し2048文字以内
  • successがtrue
  • hostnameが許可domain
  • actionがform用途と一致
  • 検証後にだけDB・mail・外部APIを実行

reCAPTCHA移行とdummy key testを行う

公式reCAPTCHA migrationでは、互換移行はreCAPTCHA v2までとされています。api.js?compat=recaptchaを使うとimplicit rendering、g-recaptcha-responsegrecaptcha APIへ合わせやすくなりますが、serverの検証先はCloudflare Siteverifyへ変更しなければなりません。TurnstileのSiteverifyはGET queryではなくPOSTだけを受け付けます。

WordPress pluginで「keyを貼ったら完了」と表示されても、問い合わせ、login、password reset、comment、checkoutを別々に送信testします。旧reCAPTCHA scriptがcacheやthemeへ残ると二重読込になり、privacy policyやCSPの記述も古いままです。移行日は旧keyをすぐ削除せず、rollback手順と変更前screenshotを残し、正常送信とspam拒否を確認してから整理します。

公式Testing資料には、常に成功、常に失敗、interactive challenge、duplicate tokenを再現するdummy sitekeyとsecret keyがあります。PlaywrightやCypressなどのautomationはbotと判定され得るため、production keyでE2E testを安定させようとしません。development、test、productionでkeyを分離し、production secretがdummy tokenを拒否することも確認します。

公開前に再現する4ケース
  • 正常tokenで1回だけ送信できる
  • 失敗tokenで本処理へ進まない
  • 5分超過・二重送信で再challengeになる
  • Siteverify停止時に無条件許可しない

error codeとAnalyticsで公開後を監視する

公式Error codesでは、110100はinvalid sitekey、110200はdomain未許可、110600はchallenge timeout、200500はiframe load errorです。300*600*はgeneric challenge failureで再試行対象になり得ます。画面へ番号だけ出さず、運用logへcode、browser、hostname、action、発生時刻を残します。

Turnstile Analyticsでは、hostname、browser、country、user agent、OS、ASN、source IPなどの偏りを確認できます。Solve rateの平均だけを見ると、一部browserで全滅していても気づけません。Free planのlookbackは最大7日なので、公開初週は毎日、その後も週1回、challenge outcomeとSiteverifyのvalid・invalid比率を確認します。

導入完了の基準はwidgetが見えることではなく、「8つの経路で正常送信でき、invalid requestが本処理へ進まず、失敗者が回復できる」状態です。対象formをPC・mobile、Chrome・Firefox・Safari系、通常・privacy extension環境で確認し、問い合わせ担当へerror時の案内を共有します。Turnstileを導入した日、widget名、hostname、key保管先、rollback手順を運用記録へ残してください。

Turnstile導入の最終チェック
  • Managed widgetとproduction hostnameを設定した
  • secretをbackendの環境変数へ保存した
  • Siteverifyでsuccess・hostname・actionを確認した
  • rate limit・honeypot・入力validationを重ねた
  • dummy keyで成功・失敗・duplicateをtestした
  • error callback・再試行・代替連絡先を用意した
  • Analyticsとserver logを確認する担当を決めた

今日の1ステップは、Cloudflare Dashboardでいきなりproduction widgetを公開することではありません。まず保護対象formを一覧にし、「問い合わせ」「login」「account作成」のどこへTurnstile、rate limit、server validationを置くか紙へ書き出してください。その設計図ができれば、keyの貼り間違いと保護漏れを減らした状態で実装を始められます。