71 人が閲覧(直近 30 日) セキュリティ

Cloudflare Turnstileの設定方法|キー発行からsiteverify検証・「呼び出されていません」警告の対処まで

Cloudflare Turnstileの設定方法|キー発行からsiteverify検証・「呼び出されていません」警告の対処まで

Cloudflare Turnstileの設定は、ダッシュボードでウィジェットを作ってサイトキーとシークレットキーを受け取る、フォームにdivを1つ置く、サーバー側でSiteverify APIにトークンを送って検証する、の3工程で完結します。つまずく人が多いのは3つ目です。公式ドキュメントは検証を省いた実装を「未完成であり、Turnstile Analyticsのトークン検証の指標はゼロになる」と書いており、ダッシュボードには「このウィジェットでは siteverify が呼び出されていません」という警告が出るようになりました。本記事では2026年10月時点の公式ドキュメントと、Siteverify APIを実際に叩いて確かめた挙動をもとに、設定の全工程と警告・エラーの読み方を整理します。

まとめ:Turnstile設定で押さえる7点

  • 設定に使うのはサイトキー(公開)とシークレットキー(サーバー専用)の2つです。「APIキー」という設定項目はありません。
  • フォームに埋め込んだだけでは保護されません。POST https://challenges.cloudflare.com/turnstile/v0/siteverify でトークンを検証して設定完了です。
  • 「siteverify が呼び出されていません」の警告は、Analyticsの検証件数とサーバーのログで本当に未実装かを切り分けます。
  • トークンの有効期限は300秒で、使えるのは1回だけです。確認画面と最終送信で同じトークンを検証したり、検証済み・期限切れのトークンを再送したりすると、timeout-or-duplicateで失敗します。
  • 常に成功するテスト用シークレットキーは、無関係な非空文字列でもsuccess:trueを返します。本番に残すと全リクエストが素通りします。
  • 2026年7月からは、ダッシュボードの「Set up with Spin」とwrangler turnstile widget createでもウィジェットを作れます。
  • 無料プランの制限は回数ではなく数です。チャレンジは無制限で、1アカウント20ウィジェット、1ウィジェット10ホスト名が上限です。

Turnstileの設定は3工程:ウィジェット・埋め込み・Siteverify

トークンを発行するウィジェットと、検証するSiteverify APIの二段構成

Turnstile(ターンスタイル、回転式改札の意)は2段階で動きます。ブラウザ側ではJavaScriptのウィジェットがチャレンジを実行し、成功すると最大2,048文字のトークンを生成します。トークンはフォーム内に自動生成されるcf-turnstile-responseという隠しinputに入り、送信と一緒にサーバーへ届きます。サーバー側はそのトークンをSiteverify APIに送り、本物か、期限内か、使い回されていないかを確かめます。

ボットがチャレンジを解いてしまっても、Cloudflareはボット的なシグナルを検知してトークンを無効と判定します。その判定を受け取れるのはサーバー側の検証だけです。公式のValidate the tokenは検証が必須である理由に「攻撃者はチャレンジを解かずに任意の文字列をフォームの送信先に送れる」ことを挙げています。

TurnstileはCDNから独立したサービスで、公式のGet startedは「Cloudflareのネットワークでプロキシしているかどうかに関係なく、あらゆるWebサイトで使える」と述べています。他社CDNやAWS上のサイト、オンプレミスのサーバーでも、DNSをCloudflareに移さずに設定できます。

WAFのマネージドチャレンジとは別プロダクト

Cloudflareのマネージドチャレンジ(Managed Challenge)はWAFルールのアクションで、Cloudflareのプロキシ配下のゾーンでしか使えません。Turnstileとは基盤(Challenge Platform)が同じだけの別プロダクトで、Turnstileのウィジェットモードの「Managed」とも別物です。WAF側でボットスコアを条件にチャレンジを返す書き方はCloudflare Adaptive Intelligenceの解説で扱っています。

ダッシュボードでのウィジェット作成とキー発行

「Add widget」から作成する6ステップ

設定の入口はCloudflareダッシュボードのTurnstileページです。公式の手順は次のとおりで、DNSやCDNの画面には触れません。

  1. Cloudflareダッシュボードで「Turnstile」ページを開く
  2. 「Add widget」を選択する
  3. 「Widget name」(識別用の名前)、「Hostname management」(ウィジェットを使うドメイン)、「Widget mode」(Managed/Non-Interactive/Invisible)を入力する
  4. SPAで使うなら「Pre-clearance support」を設定する(任意)
  5. 「Create」を選択して保存する
  6. 表示されたサイトキーとシークレットキーをコピーし、シークレットキーを安全な場所に保管する

ウィジェット1つが、サイトキーとシークレットキーの1組に対応します。フォームごとに設定を変えたいならウィジェットを分けます。作成後の変更は、ウィジェットを開いて「Settings」から行います。

「Set up with Spin」とwrangler CLIでの作成(2026年7月追加)

2026年7月、作成手順にTurnstile Spinが加わりました。Turnstileページのヘッダーにある「Set up with Spin」でドメインを入力すると、ウィジェットが作られ、サイトキーとシークレットキー、AIコーディングエージェント向けのプロンプトが表示されます。プロンプトにはサイトキーしか入っておらず、シークレットキーは含まれません。注意したいのは、Spinがlocalhostと127.0.0.1を許可ホスト名に自動で追加する点です。公式は同じページで「Siteverifyが返すホスト名をバックエンドで照合し、本番ではローカルのホスト名を許可しないこと」と求めています。Spinで作ったウィジェットをそのまま本番に使うなら、後述のhostname照合は省けません。

ターミナルから作る場合はWranglerを使います。wrangler turnstile widgetコマンドはWranglerの変更履歴によると4.109.0(2026年7月9日公開)で追加されました。

npx wrangler@latest turnstile widget create "contact-form" \
  --domain example.jp --domain www.example.jp \
  --mode managed

2026年10月8日公開の4.149.0で--helpを確認すると、コマンドには「alpha」の表記が付いていました。必須は--domainと--modeの2つです。--regionはworldかchinaを選ぶ項目で、ヘルプには「作成後は変更できない」と書かれています。実行するとサイトキーとシークレットキーが表示され、listとupdateの出力ではシークレットが伏せられ、getでは表示されます。以前からwrangler loginしていた環境では、Turnstile用の権限を得るために再ログインが必要です。APIトークンで使う場合はAccount.Turnstile:Editの権限を付けます。

Managed/Non-Interactive/Invisibleの選び分け

モード 訪問者に見えるもの 操作 向く場面
Managed(推奨) ウィジェット リスクに応じチェックボックス 迷ったらこれ
Non-Interactive ウィジェットとスピナー 常に不要 摩擦を避けたいフォーム
Invisible 何も表示されない 常に不要 デザインを崩したくない場面

要件が固まっていないならManagedを選んでください。Invisibleは見た目が最もすっきりしますが、後述するプライバシーポリシーへの記載が有効化の条件で、デザイン上の強い理由がないのに選ぶと法務確認の手間だけが増えます。

サイトキーとシークレットキー(「APIキー」という項目は存在しない)

ウィジェットを作ると、サイトキー(sitekey)とシークレットキー(secret key)が1組で発行されます。サイトキーはHTMLに書く公開識別子で、ページのソースから見えて構いません。シークレットキーはサーバーがSiteverify APIのsecretパラメータに渡す秘密鍵です。「APIキー」という項目は無く、CloudflareのAPIトークンとも別物です。

シークレットキーをフロントエンドのコードに置くのは最も危険な設定ミスで、公式は「攻撃者がセキュリティチェックを回避できる」と警告しています。漏えいが疑われるときはローテーションします。ローテーションAPIはinvalidate_immediatelyをfalseにすると旧シークレットを2時間有効に残し、その猶予中は再ローテーションできません。新しいシークレットを発行し、デプロイし、検証が通ることを確かめてから旧シークレットの失効を待つ順番なら、フォームを止めずに差し替えられます。順番を逆にすると、デプロイが終わるまで全送信が失敗します。

ホスト名の登録と、ウィジェット数の上限

ウィジェットには許可ホスト名を登録します。無料プランは1ウィジェットあたり10件、Enterpriseは200件までです。登録していないドメインで表示するとエラーコード110200(Domain not authorized)になります。wwwありだけを登録してwwwなしで開いた、ステージングのドメインを入れ忘れた、が典型的な原因です。

先に当たりやすいのは、1アカウント20ウィジェットという上限です。フォームごとに分けると20は早く埋まるため、本番とステージングで分けるのか、機能ごとに分けるのかを最初に決めてください。ローカル開発には本番のサイトキーを使わず、後述のテスト用キーを使うのが公式の想定です。

Pre-clearanceを設定してWAFチャレンジを省く場合の前提

Pre-clearanceは、Turnstileを通過した訪問者に同じゾーン内のWAFチャレンジを繰り返し出さないための任意設定です。cf_clearanceクッキーによって、以降のリクエスト(APIリクエストを含む)でWAFチャレンジを省けます。条件は、対象ホスト名がCloudflareに登録済みのゾーンであることと、WAFルールを持つゾーンをウィジェット側で選ぶことです。example.comとapp.example.comを登録していてapp.example.comにクリアランスを出したいなら、選ぶのはapp.example.comです。CDN非依存のTurnstileで、Cloudflareのゾーン登録が前提になるのはこの機能だけです。

フロントエンドへのウィジェット埋め込み

静的ページ向けの暗黙的レンダリング

暗黙的レンダリングは、スクリプトがcf-turnstileクラスの要素を探して自動で描画する方式です。静的サイトや素直なフォームならこれで足ります。

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

<form action="/login" method="POST">
  <input type="email" name="email">
  <div class="cf-turnstile" data-sitekey="0x4AAAAAAA_YOUR_SITEKEY" data-action="login"></div>
  <button type="submit">送信</button>
</form>

divをform要素の内側に置くと、トークンを入れる隠しinputが自動で作られます。form外に置くならcallbackかturnstile.getResponse(widgetId)でトークンを受け取り、自分で送信してください。api.jsは公式が「示された正確なURLから読み込むこと。プロキシやキャッシュをすると将来の更新時に失敗する」としているので、自社CDNに取り込んで配信しないでください。

SPA・動的フォーム向けの明示的レンダリング

ReactやVueのようにフォームが後から描画される画面では、スクリプトURLに?render=explicitを付け、好きなタイミングでturnstile.render()を呼びます。ここで多い事故が実行順です。defer付きの外部スクリプトはパース完了後に動くのに、インラインのスクリプトはその場で動くため、そのままturnstile.render()を書くとapi.jsより先に走ってReferenceErrorになります。公式が示す安全な形は、onloadパラメータでコールバック名を渡す方式です。

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=onTurnstileLoad" defer></script>
<div id="cf-widget"></div>
<script>
  function onTurnstileLoad() {
    turnstile.render('#cf-widget', {
      sitekey: '0x4AAAAAAA_YOUR_SITEKEY',
      action: 'login',
      callback: function (token) { console.log('token:', token); },
      'error-callback': function () { console.log('challenge failed'); }
    });
  }
</script>

送信に失敗して同じフォームを再送させる場合、トークンは1回しか使えないのでturnstile.reset()で取り直させます。コールバックの名前は紛らわしく、timeout-callbackは対話型チャレンジが時間内に解かれなかったときに呼ばれてウィジェットをリセットしますが、expired-callbackはトークン失効時に呼ばれてもリセットしません。actionに渡せる値は英数字と_・-の32文字までで、日本語やスラッシュは通りません。

サイズ・テーマ・言語の指定

size 幅 高さ 用途
normal(既定) 300px 65px 標準
flexible 100%(最小300px) 65px レスポンシブ
compact 150px 140px サイドバーなど

flexibleは2024年8月12日に追加された値で、古い解説にはnormalとcompactしか載っていません。サイズ指定が効くのはManagedとNon-Interactiveだけです。テーマはdata-themeでlight・dark・auto(既定)から選び、表示言語はdata-languageにjaを指定すると日本語に固定できます。

WordPress(Contact Form 7)で設定する場合

Contact Form 7は6.1(2025年6月26日リリース)でTurnstile連携を本体に取り込みました。管理画面の「お問い合わせ」から「インテグレーション」を開き、2つのキーを貼るだけで設定できます。この方式ではSiteverifyの呼び出しをプラグインが代行するため、自分でサーバー側のコードを書く必要はありません。ただし後述の警告がダッシュボードに出ることがあり、その読み方は別の章で説明します。

サーバー側のSiteverify呼び出しとトークン検証

Node.jsでの実装例(テスト用キーで実行を確認済み)

検証はPOST https://challenges.cloudflare.com/turnstile/v0/siteverifyに送ります。必須はsecretとresponse(フォームから届いたcf-turnstile-responseの値)、任意でremoteipとidempotency_keyを渡せます。リクエストはフォームエンコードとJSONの両方を受け付け、レスポンスは常にJSONです。次はNode.js組み込みのfetchだけで書いた検証関数で、Node.js v26.5.0上でテスト用シークレットキー3種を渡し、成功・失敗・使用済みの各分岐に入ることを確かめました。

import { randomUUID } from 'node:crypto';

const SITEVERIFY = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';

export async function verifyTurnstile(token, { secret, remoteip, expectedHostname, expectedAction }) {
  if (typeof token !== 'string' || !token || token.length > 2048) return { ok: false, codes: ['missing-or-too-long'] };
  const idempotencyKey = randomUUID();
  for (let attempt = 0; attempt < 2; attempt++) {
    const res = await fetch(SITEVERIFY, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ secret, response: token, remoteip, idempotency_key: idempotencyKey }),
      signal: AbortSignal.timeout(10000),
    });
    // 400や405でもボディに理由が入るので、ステータスより先にJSONを読む
    const out = await res.json();
    const codes = out['error-codes'] ?? [];
    // internal-errorだけは同じidempotency_keyで1回だけ再試行する
    if (codes.includes('internal-error') && attempt === 0) continue;
    if (!out.success) return { ok: false, status: res.status, codes };
    if (out.metadata?.result_with_testing_key && process.env.NODE_ENV === 'production') {
      return { ok: false, codes: ['testing-key-in-production'] };
    }
    if (expectedHostname && out.hostname !== expectedHostname) return { ok: false, codes: ['hostname-mismatch'] };
    if (expectedAction && out.action !== expectedAction) return { ok: false, codes: ['action-mismatch'] };
    return { ok: true, hostname: out.hostname };
  }
  return { ok: false, codes: ['internal-error'] };
}

remoteipに渡すIPの取り方は環境で変わります。Cloudflareのプロキシ配下ならCF-Connecting-IPヘッダーを使えますが、これはCloudflare経由のリクエストにしか付きません。AWSのALBや他社CDNの配下ではX-Forwarded-Forなど、その環境の仕様に合わせてください。

hostname・action照合によるトークン転用対策

Siteverifyのレスポンスにはsuccess・challenge_ts・hostname・error-codes・action・cdataが入り、Enterpriseではmetadata.ephemeral_idも返ります。実装上の勘所はhostnameとactionです。ログインと問い合わせで同じシークレットキーを使っているなら、actionを照合すれば「問い合わせフォームで得たトークンをログインAPIに投げる」転用を弾けます。successだけを見て通すと、この経路が空いたままです。

ここで1つ落とし穴があります。上のコードで確かめたところ、テスト用シークレットキーで検証するとhostnameは常にexample.comで返り、actionは返りませんでした。このため照合を入れた上の関数は、期待するホスト名がexample.com以外ならhostname-mismatchになり、ホスト名が一致しても期待するactionを指定するとaction-mismatchになります。開発環境では照合の期待値を環境変数で外すか、テスト用キーのときだけ照合を省く分岐を入れてください。

トークンは300秒・1回限りという制約

トークンの有効期限は生成から300秒で、検証できるのは1回だけです。有効なトークンでも、2回目の検証はsuccess:falseになります。既定ではトークン失効時に自動更新されるため、記入時間が5分を超えるだけで失敗するわけではありません。問題になるのは、自動更新を無効にしたり更新後も古いトークンを保持したりするフォーム、確認画面と送信の両方で検証してしまう多段フォーム、入力エラーで戻したあと同じトークンで再送させる実装です。検証は最終送信の1回だけにし、失敗したらturnstile.reset()で新しいトークンを取り直させてください。

error-codesの読み分けと、HTTPステータスが200でない場合

error-code 意味 典型的な原因
missing-input-secret secretが無い 環境変数の未設定
invalid-input-secret secretが不正 サイトキーを渡している
missing-input-response responseが無い divがform外
invalid-input-response トークンが不正・期限切れ 本番鍵とテスト鍵の混在
bad-request リクエスト不正 メソッドや形式の誤り
timeout-or-duplicate 期限切れ・検証済み トークンの二重検証
internal-error Cloudflare側の一時障害 再試行

アプリが「invalid turnstile token」のような独自のメッセージを出している場合、原因はSiteverifyのerror-codesのほか、通信失敗やアプリ独自のhostname・action照合の失敗もありえます。ログにerror-codesをそのまま残しておけば、原因を表と突き合わせられます。

見落としやすいのがHTTPステータスです。実際に叩くと、secretの欠落や存在しないシークレットは400、GETは405で返り、405のボディには公式のレスポンス項目表に無いmessages配列で"This API expects POST requests."と理由が入っていました。一方、テスト用キーでの検証失敗や使用済みは200で返ります。if (!res.ok) throwと書くと理由を読まずに捨てるので、HTTP応答が届いた場合はJSONを読んで分岐させます。通信失敗・タイムアウト・JSON解析失敗では掲載関数が例外を送出するため、呼び出し元で捕捉し、保護対象の処理を中止してエラーを記録してください。internal-errorのときに同じリクエストを単純に再送すると二重検証と見なされるおそれがあるため、2023年5月25日に追加されたidempotency_key(UUID)を付けて再試行します。

「このウィジェットでは siteverify が呼び出されていません」と表示されたとき

Siteverify警告が示すトークン発行と検証の不一致

ダッシュボードのTurnstile画面に「このウィジェットでは siteverify が呼び出されていません」と出て、「サーバー側で siteverify が呼び出されないと、ウィジェットトークンは検証されません。」と続く警告があります。英語表示では「Siteverify isn’t being called for <サイト名>. Without a server-side siteverify call, widget tokens aren’t validated. The forms these widgets protect remain open to bots.」です。ウィジェットがトークンを発行しているのに、そのトークンをサーバーが検証した形跡が見えないという意味で、本当に未実装ならフォームはボットに対して開いたままです。

どんな条件で表示されるかは公式ドキュメントに書かれていません。手がかりになるのが、WordPress.orgのContact Form 7のサポートフォーラムに2026年8月12日に投稿された相談です。Contact Form 7でTurnstileを設定して動いているのにこの警告が出たという質問に、プラグイン作者は「Contact Form 7は送信のたびにsiteverifyを呼ぶが、Turnstileはページを表示するたびにトークンを作る。送信されずに終わるトークンは検証されないので、これは通常の挙動」と回答しています。つまり、警告が出ていても実装が正しい場合があります。

誤検知か本当の未実装かを見分ける4つの確認

  1. Turnstile Analyticsの「Token validation」でSiteverify requestsの件数を見る。公式の説明ではValid tokensとInvalid tokensの内訳も出ます。0なら未実装か、検証が届いていません。
  2. テスト用でない本番のキーでフォームを1回送信し、サーバーのログにSiteverifyの応答(successとerror-codes)が出るかを確かめる。応答が記録されない場合は、検証処理への未到達に加え、通信失敗・タイムアウト・ログ設定の不備を切り分けてください。
  3. サーバーが使っているシークレットキーが、警告の出ているウィジェットのものかを確かめる。ウィジェットを作り直したのに環境変数が古いまま、という取り違えがあります。
  4. 検証件数が閲覧数より少ないこと自体は異常ではありません。ただし、それだけでは一部フォームの検証漏れを除外できないため、保護対象の各送信経路でSiteverifyが呼ばれ、検証失敗時に処理が拒否されることを確認してください。

判断を誤りやすいのは、2の確認を飛ばして「プラグインを入れたから大丈夫」と結論することです。WordPressのプラグインやフォームサービスの中には、ウィジェットの表示だけを担い、検証は別の設定で有効にするものもあります。対象期間に保護対象のフォームを送信してもSiteverify requestsが0のままなら、対象ウィジェット・検証除外設定・サーバーからの通信を確認し、検証が実行されていることを確かめてください。

検証件数が落ちたら気づける監視の置き方

Siteverifyを呼ぶコードが後の改修で消えても、訪問者からは違いが見えません。検証件数の監視に加え、不正トークンを送って拒否を確認する回帰テストも検証漏れの検出に使えます。無料プランではAnalyticsを7日分しか遡れないため、上のコードの戻り値(okとcodes)を1行ずつログに残し、フォームの送信数に対して検証件数が急に減ったら通知する仕組みを置いておくと安全です。デプロイ直後にinvalid-input-responseが急増したときは、フロントエンドがトークンを送らなくなった可能性を疑います。

ダミーキーによる動作確認とE2Eテストの環境分離

テスト用サイトキー・シークレットキーの一覧と挙動

本番のキーでも失敗時の挙動は確認できますが、成功・失敗を決められた条件で再現するにはテスト用キーを使います。Cloudflareが公開しているテスト用キーは、ドメインを問わずlocalhostでも動きます。

テスト用サイトキー 挙動 ウィジェット
1x00000000000000000000AA 常に成功 可視
2x00000000000000000000AB 常に失敗 可視
1x00000000000000000000BB 常に成功 不可視
2x00000000000000000000BB 常に失敗 不可視
3x00000000000000000000FF 対話を強制 可視
テスト用シークレットキー 挙動
1x0000000000000000000000000000000AA 常に検証成功
2x0000000000000000000000000000000AA 常に検証失敗
3x0000000000000000000000000000000AA 使用済み(timeout-or-duplicate)

テスト用サイトキーが生成するトークンはXXXX.DUMMY.TOKEN.XXXXという固定の文字列で、本番のシークレットキーはこれを拒否します。2xで始まるサイトキーはウィジェット自体がエラーになりトークンを出さないため、サーバー側の失敗分岐を試す用途には使えません。

常時成功用シークレットキーの検証挙動と本番混入リスク

ここは公式ドキュメントに書かれていない挙動です。2026年10月13日にSiteverify APIを叩き直したところ、テスト用シークレットキー1x0000000000000000000000000000000AAは、responseにhelloのような無関係な文字列を渡してもsuccess:trueを返しました。2026年7月に確認したときと同じ結果です。空文字を渡したときだけはmissing-input-responseで失敗しました。

テスト用シークレットキーを本番に残すと、ボットは適当な文字列を送るだけで検証を通過し、例外もエラーコードも出ません。見分ける手がかりはレスポンスにあり、テスト用キーで検証したときだけmetadata.result_with_testing_keyがtrueで返ります。この項目も公式のレスポンス項目表に載っていません。上のNode.jsの例は、本番環境でこの値がtrueなら失敗扱いにしています。

PlaywrightやCypressがブロックされるときのCI設定

自動テストのブラウザはTurnstileにボットとして検知されます。公式のE2EテストのチュートリアルもSelenium、Cypress、Playwrightを名指ししています。本番キーのままCIを回して「たまに落ちるテスト」として放置せず、環境変数で鍵を切り替えてください。

# .env.development および CI(正常系)
TURNSTILE_SITEKEY=1x00000000000000000000AA
TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA

# サーバー側の検証失敗を試す(サイトキーは成功側のまま)
TURNSTILE_SITEKEY=1x00000000000000000000AA
TURNSTILE_SECRET_KEY=2x0000000000000000000000000000000AA

# クライアント側のエラー表示を試す
TURNSTILE_SITEKEY=2x00000000000000000000AB

# .env.production
TURNSTILE_SITEKEY=(ダッシュボードで発行したサイトキー)
TURNSTILE_SECRET_KEY=(ダッシュボードで発行したシークレットキー)

サーバーの失敗分岐を試すなら、サイトキーは成功側の1xのまま、シークレットキーだけ2xに替えます。本番では意図して起こしにくいtimeout-or-duplicateは、3xのシークレットキーなら確実に再現できます。

ウィジェットが表示されない・失敗するときのエラーコード

クライアント側エラーコードの早見表

ウィジェットが描画されない、エラー表示になる、というときは数値のエラーコードが出ます。末尾が*のものは残りの桁が変わります。表は2026年9月25日更新の公式のエラーコード一覧に沿っています。

コード 意味 再試行 対処
110100 / 400020 サイトキーが無効 不可 ダッシュボードで確認
110110 サイトキーが見つからない 不可 綴りを確認
110200 ドメインが未許可 不可 ホスト名に追加
110600 チャレンジのタイムアウト 可 訪問者の時計ずれ
110620 操作のタイムアウト 可 turnstile.reset()
200100 時計またはキャッシュ 不可 中間キャッシュを疑う
200500 iframeの読み込み失敗 可 接続先の遮断を確認
400021 サイトキーのリージョン不一致 不可 scriptタグの読込先を確認
400070 サイトキーが無効化済み 不可 ダッシュボードで確認
300* / 600* チャレンジ失敗(全般) 可 ボット的挙動の検知

2026年7月時点の一覧に無かったのが400021で、公式は「サイトキーのリージョンがTurnstileのscriptタグのドメインと一致しない」と説明しています。前述のとおりウィジェットにはworldとchinaのリージョンがあり、作成後は変えられません。200500はiframeの読み込み失敗を示し、自社サイトのCSP設定に加え、訪問者側の通信制限も確認が必要です。訪問者側では、社内プロキシや広告ブロッカーがchallenges.cloudflare.comを遮断しているときに出ます。偽のCAPTCHA画面でコマンドを実行させる攻撃も出回っているので、正規のTurnstileの振る舞いはClickFixの偽CAPTCHAでコマンドを実行させる手口と検知・防御とあわせて押さえておくと見分けやすくなります。

CSPとネットワークの許可リスト

CSPヘッダーを配信しているサイトでは、script-srcとframe-srcにhttps://challenges.cloudflare.comを追加する許可方法があります。Cloudflareの推奨はnonce方式で、api.jsのscriptタグに付けたnonceはTurnstileが動的に読み込むリソースにも伝わります。Pre-clearanceを使うならconnect-srcに'self'も要ります。

社内ネットワークなどで通信先を許可リストで絞っている場合は、2026年7月22日の変更履歴も確認してください。Turnstileはブラウザの検証の一部としてhagen.challenges.cloudflare.comとbrunhild.challenges.cloudflare.comへも通信するようになり、公式はこの2つを許可リストに加えるよう求めています。一方で、開発者ツールにchallenges.cloudflare.comやdnstest.dev配下のサブドメインへの通信がDNSの名前解決エラーで失敗している記録が残るのは想定内の挙動で、訪問者を止めるものではありません。WebViewに組み込んでいる場合、これを致命的なエラーとして扱わないよう公式は注意しています。

コンソールに出る401はPrivate Access Tokensの想定内の挙動

開発者ツールで/cdn-cgi/challenge-platform/.../pat/...へのリクエストが401で返っていても、実装の不具合ではありません。ウィジェットがPrivate Access Token(PAT)を取りに行き、訪問者の端末やネットワークが提供できなかったときの想定内の応答です。取れなければ通常のチャレンジに切り替わるだけなので、トークンを受け取れているなら401は無視して構いません。

無料プランの上限とInvisibleモードのプライバシー条件

無料プランの制限は回数ではなく数と機能

公式のプラン比較表(2026年8月14日更新)では、無料プランの「無制限のチャレンジ(トラフィックまたは検証リクエスト)」がYesです。制限は回数ではなく、次の数と機能に置かれています。

項目 Free Enterprise
料金 無料 要問い合わせ
ウィジェット数 20個まで 無制限
チャレンジ・検証リクエスト 無制限 無制限
ホスト名 10件/ウィジェット 200件/ウィジェット
ホスト名の事前登録の省略 不可 可
Analyticsの参照期間 最大7日 最大30日
Ephemeral IDs 不可 可
ブランド表記の削除 不可 可

「月100万siteverifyリクエストまで無料」という数字をよく見かけますが、出典の2023年9月29日のGA告知に「月あたり」の語は無く、現行のプラン比較表にも回数の上限はありません。アクセシビリティの表記は公式内で割れており、プラン比較表は「WCAG 2.2 AAA」、製品概要ページは「WCAG 2.2 AA」です。監査で根拠を求められたらAAを前提に置いてください。Cloudflareへの依存度そのものの検討には、Cloudflareの障害・SLA・情報漏えいの実態と対策も参考になります。会員登録やログインの画面にボット対策を組み込む開発は、会員管理システム開発で相談を受け付けています。

Invisibleモードを選ぶならプライバシーポリシーへの記載が条件

Cloudflareは「Invisibleモードを有効にする条件として、自社のプライバシーポリシーでTurnstile Privacy Addendumを参照しなければならない」と定めています。技術的なチェックはありませんが規約上の条件で、ポリシー改訂の稟議が通らない段階でInvisibleを選ぶべきではありません。ManagedとNon-Interactiveには、この条件はありません。EnterpriseのOfflabel(ブランド表記の削除)にも同じ条件があり、こちらはウィジェットにCloudflareのプライバシーポリシーへのリンクを表示させる選択肢も用意されています。

よくある質問

reCAPTCHAやhCaptchaから移行できますか?

できます。公式はTurnstileを「ドロップイン置換」と位置づけています。reCAPTCHAには互換モードがあり、api.js?compat=recaptchaで読み込むと隠しinputの名前がg-recaptcha-responseになるため、フォーム側のコードを触らずに済みます。互換の対象はreCAPTCHA v2までで、v3のスコア方式は含まれません。サーバー側の検証先とレスポンス形式は違うので、siteverifyの呼び出しは書き換えが必要です。

無料プランのウィジェット20個という上限は引き上げられますか?

無料のままでは増やせません。公式の注記では、Enterprise Bot ManagementとEnterprise Turnstileの契約者がアカウントチーム経由で引き上げを相談できます。20個で足りなくなりそうなら、フォーム単位ではなく用途単位でウィジェットをまとめ、actionでフォームを区別する設計に切り替えるほうが現実的です。無料からEnterpriseへ移っても、既存のウィジェットと設定は引き継がれます。

TurnstileはGDPRに対応していますか?

CloudflareはTurnstile専用のPrivacy Addendumを公開していますが、それで利用者側の対応が不要になるわけではありません。EU圏の訪問者を扱うなら、自社のデータ処理の記録を整えたうえで、Invisibleを使う場合はAddendumへの参照をプライバシーポリシーに入れてください。

Turnstileの認証で訪問者は何を求められますか?

Managedモードでリスクが高いと判定されたときにチェックボックスを1回選ぶだけで、画像を選ぶような作業はありません。Non-InteractiveとInvisibleでは操作を求められず、アカウント登録も要りません。

Cloudflareを使っていないサイトやAWS上のサイトでも設定できますか?

できます。公式は「Cloudflareのネットワークでプロキシしているかどうかに関係なく使える」と明記しており、AWSや他社CDN、オンプレミスでも、DNSを移さずに導入できます。例外はPre-clearanceだけで、Cloudflareに登録済みのゾーンが前提です。AWS上のサイトでは、remoteipに渡すIPをALBやCloudFrontが付けるX-Forwarded-Forから取得する構成があります。ヘッダー全体や先頭の値を無条件に信用せず、信頼するプロキシ構成とヘッダーの追加規則に従ってクライアントIPを特定してください。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.15 テックブログ リゾートトラスト不正アクセス:会員2.6万件・カハラ横浜3.6万件の対象確認と窓口
  2. 2025.09.03 コラム ストループ効果とは?原典1935年の実測値と年齢別の平均・テストのやり方
  3. 2026.01.20 テックブログ PineDramaとは?TikTokショートドラマアプリの料金・安全性とTikTok内ミニドラマの課金
  4. 2026.04.13 コラム ゴルフ賞金の税金はいくら?1,000万円で源泉194万円・確定申告での精算と副賞の車、海外賞金【令和8年分】
  5. 2026.10.16 テックブログ バーガーキング情報漏洩どうしたらいい?約1,118万件の対象確認・補償・退会とメールの真偽

RELATED POSTS 関連記事

目次