このブログのお問い合わせフォームは、スパム対策を Google の reCAPTCHA から Cloudflare Turnstile に切り替えました。きっかけは、ブログを静的HTMLで公開する構成にしたときに、フォームの送信処理を自作したことです。
reCAPTCHA には何年もお世話になりましたが、使っていて気になる点がいくつかありました。この記事では、reCAPTCHA で困っていたこと、Turnstile を選んだ理由、自作フォームへの組み込み方と、使ってみて気をつける点をまとめます。
静的化の全体の話は「WordPressを静的HTML化して本番公開するまで」に書いています。
reCAPTCHAで困っていたこと
以前は Snow Monkey Forms に reCAPTCHA を設定していました(設定方法は「SnowMonkeyForms|フォームにreCAPTCHAを設置する方法」)。実際に困っていたのは次の3つです。
1. 右下のバッジと、表示速度への影響
reCAPTCHA v3 を入れると、画面の右下に reCAPTCHA のバッジが常に表示されます。スマホではページ上部へ戻るボタンなどと重なりやすく、デザインの邪魔になりがちでした。
バッジは、reCAPTCHA で保護していることをページ内に明記すれば CSS で非表示にできます(執筆時点の Google の FAQ による)。ただ、毎回その文言を入れる手間がかかります。
もう1つは表示速度です。reCAPTCHA v3 は、サイト内での動きを見てスコアを出す仕組みのため、フォームのないページにもスクリプトを読み込む設定にしているサイトがよくあります。そうすると、全ページで Google のスクリプトを読み込むことになり、PageSpeed Insights の点数にも影響します。
2. スパムが止まりきらない
reCAPTCHA v3 はチェックボックスや画像選択を出さない代わりに、0.0〜1.0 のスコアで「人間らしさ」を判定し、どこで弾くかはサイト側が決めます。しきい値を厳しくすると本物のお問い合わせまで弾く心配があり、ゆるくすると営業メールやスパムがそのまま届きます。結局、入れていても怪しいメールは届き続けていました。
3. Google Cloudへの移行と無料枠
いちばん大きかったのがこれです。reCAPTCHA は Google Cloud のサービスに統合され、従来のキー(Classic キー)は Google Cloud のプロジェクトに移行されることになりました。Google のドキュメントでは、2025年第4四半期から自動移行が始まり、2026年第1四半期に完了するとされています。
移行後も既存のキーはそのまま動きますが、キーの管理は Google Cloud コンソールで行うことになり、プロジェクトの所有権の招待を受け入れる必要があります。また、無料で使えるのは月10,000件の評価までで、それを超えて使うには課金の設定が必要です(執筆時点)。複数のクライアントサイトを1つのアカウントで管理していると、「どのプロジェクトに、どのサイトのキーが入っているか」を整理するだけでも一仕事です。
Turnstileを選んだ理由
Cloudflare Turnstile は、Cloudflare が提供する reCAPTCHA の代わりになる仕組みです。選んだ理由は次のとおりです。
- 無料プランで十分:執筆時点の公式ドキュメントでは、無料プランでもウィジェット20個まで、1ウィジェットにホスト名10個まで設定でき、チャレンジの回数に上限はありません
- サイトを Cloudflare 経由にする必要がない:DNSやサーバーはそのままで、フォームに埋め込むだけで使えます
- 訪問者の手間が少ない:推奨の「Managed」モードでは、ほとんどの人はチェックボックスを押すだけか、何もしなくても通過します
- バッジが出ない:ウィジェットはフォームの中にだけ表示されます
- 使うページにだけ読み込めばいい:スコアを出す仕組みではないので、フォームのあるページだけでスクリプトを読み込めば済みます
このブログでも、Turnstile のスクリプトを読み込んでいるのはお問い合わせページだけです。ほかのページでは読み込んでいません。
自作フォームへの組み込み方
静的化したこのブログでは、PHP が動くのはフォームの送信処理の1ファイルだけです。Turnstile は「ブラウザでの表示」と「サーバーでの検証」の2か所に手を入れます。
フォーム側:ウィジェットを置く
Cloudflare のダッシュボードで Turnstile のウィジェットを作り、サイトキーとシークレットキーを発行します。フォームには、スクリプトとウィジェット用の要素を置くだけです。
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<form action="/contact-send.php" method="post">
<!-- 入力欄 -->
<div class="cf-turnstile" data-sitekey="サイトキー" data-language="ja" data-theme="light"></div>
<button type="submit">送信する</button>
</form>
ウィジェットの検証が終わると、フォームの中に cf-turnstile-response という名前でトークンが自動的に入り、ほかの入力内容と一緒に送信されます。
サーバー側:siteverifyで検証する
ここが一番大事なところです。ウィジェットを置いただけでは、スパム対策にはなりません。送られてきたトークンを、サーバーから Cloudflare の siteverify API に送って確認する必要があります。PHP で書くと、たとえば次のような処理です。
<?php
// シークレットキーは公開フォルダの外に置いた設定ファイルから読み込む
$config = require '/path/to/private/config.php';
$token = $_POST['cf-turnstile-response'] ?? '';
$ch = curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'secret' => $config['turnstile_secret'],
'response' => $token,
'remoteip' => $_SERVER['REMOTE_ADDR'] ?? '',
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$result = json_decode((string) curl_exec($ch), true);
curl_close($ch);
if (empty($result['success'])) {
// 検証に失敗したら送信しない
http_response_code(400);
exit('送信を確認できませんでした。お手数ですが、もう一度お試しください。');
}
// ここからメール送信の処理
シークレットキーは、公開フォルダの外に置いた設定ファイルから読み込むようにしています。送信先のメールアドレスも同じ設定ファイルに入れて、公開されるファイルには書かないようにしました。
あわせて入れたハニーポット
Turnstile とは別に、人には見えない入力欄(ハニーポット)も入れています。画面には表示せず、「この欄は空のままにしてください」とラベルを付けておき、ここに何か入っていたら機械的な送信とみなして送信しない、という単純な仕組みです。手間がかからないわりに効果があるので、Turnstile と組み合わせておくと安心です。
ダッシュボードで実際の結果を見る
Cloudflare のダッシュボードでは、ウィジェットごとにチャレンジの結果をグラフで確認できます。下は、このブログのお問い合わせフォームの7日間の画面です。

- Challenges issued(チャレンジの発行数):49件
- Challenges solved / unsolved(解決・未解決):解決 41件、未解決 8件
- Likely human / Likely bot:人間らしい 83.67%、botらしい 16.33%
- Token validation(トークンの検証):41件
グラフを見ると、昼間にまとまって表示されたチャレンジはほとんどが解決(緑)で、人間によるアクセスと判定されています。一方、金曜と土曜の早朝に2件ずつある未解決(赤)は、どちらも100%「botらしい」と判定されていました。チャレンジを解けなければトークンは発行されないので、こうしたアクセスがトークンなしで送信してきても、サーバー側の siteverify で弾かれます。
もう1つ見ておきたいのが、右上の「Token validation」です。解決された41件と同じ数が表示されていて、サーバー側の siteverify がきちんと動いていることが確認できます。ここが0のままなら、ウィジェットは表示されていてもサーバーで検証していない、つまりスパム対策として機能していない状態です。
個人ブログなので件数は多くありませんが、reCAPTCHA v3 のようにスコアのしきい値で悩まなくても、「人間は通し、botらしいアクセスは通さない」結果がそのまま見えるのは安心できるところです。導入したら、自分でテスト送信して solved と Token validation に数字が入るかを確認しておくと、設定ミスにもすぐ気づけます。
使ってみて気をつけること
- トークンには有効期限がある:公式ドキュメントでは、トークンの有効期限は5分で、検証できるのは1回だけです。入力に時間がかかる長いフォームでは、送信時に期限切れになることがあります。エラー時に「もう一度お試しください」と案内するか、ウィジェットを再表示する処理を入れておきます
- スコアはない:reCAPTCHA v3 のように点数で判断するのではなく、成功か失敗かだけが返ってきます。v3 のしきい値の設定は引き継げないので、
successが false なら弾く、と考えます - サーバー側の検証を忘れない:Cloudflare のダッシュボードの分析で検証数が0のままなら、siteverify が動いていない可能性があります
- プライバシーポリシーの見直し:外部のサービスに訪問者の情報が送られる点は reCAPTCHA と同じです。プライバシーポリシーに Cloudflare Turnstile を使っていることを書いておきます
WordPressのフォームプラグインなら設定だけで使える
自作フォームでなくても、Turnstile は使えます。たとえば Contact Form 7 では、「お問い合わせ → インテグレーション」に Turnstile の項目があり、サイトキーとシークレットキーを入れるだけで、すべてのフォームにウィジェットが表示されます。表示位置を変えたいときは、フォームに [turnstile] タグを入れます。Contact Form 7 の公式サイトでも、特別な理由がなければ reCAPTCHA より Turnstile をすすめています。
ほかのフォームプラグインでも Turnstile に対応しているものが増えているので、reCAPTCHA の Google Cloud 移行をきっかけに見直してみるのもおすすめです。
まとめ
- reCAPTCHA は、バッジ・全ページでのスクリプト読み込み・スパムのすり抜け・Google Cloud 移行と無料枠の管理が気になっていた
- Turnstile は無料プランで十分に使え、サイトを Cloudflare 経由にしなくても導入できる
- 自作フォームでは、ウィジェットの設置に加えて、サーバー側での siteverify の検証が必須
- シークレットキーは公開フォルダの外に置き、ハニーポットも組み合わせる
- Contact Form 7 などのプラグインなら、キーを入れるだけで切り替えられる
料金や無料枠、移行のスケジュールは今後も変わる可能性があります。導入するときは、Cloudflare と Google の公式ドキュメントで最新の情報を確認してください。
