Bot Fight Mode を無料プランで使うと GitHub Actions が 403 になる
GitHub Actions で 8 サイトのトップページを 1 日 2 回 GET する死活チェックを組み(Google Analytics を入れずに Cloudflare でブログを監視する)、初回実行でいきなり 2 サイトが 403 でした。2026 年 7 月 18 日のログです(このブログ以外のドメインは伏せています)。
OK https://site-a.example/ 200 3147ms
OK https://site-b.example/ 200 3680ms
NG https://site-c.example/ 403 (リトライ後)
OK https://site-d.example/ 200 1585ms
OK https://site-e.example/ 200 3622ms
NG https://shinobiworks.com/ 403 (リトライ後)
OK https://site-f.example/ 200 3460ms
OK https://site-g.example/ 200 2252ms8 サイトは全部 Cloudflare Workers 上の同じ構成で、同じスクリプトが同じ User-Agent(emdash-uptime-check/1.0)で叩いています。ブラウザで開けば 200 が返ります。403 になった 2 サイトだけに共通していたのは、Cloudflare の Bot Fight Mode を有効にしていたことでした。
11 分後に、ダッシュボードで Bot Fight Mode を切って同じコミットのまま再実行しました。
OK https://site-c.example/ 200 839ms
OK https://shinobiworks.com/ 200 4063msコードを 1 行も変えずに、全 8 サイトが 200 になりました。
判定されたのは「ブラウザではないこと」
GitHub Actions のランナーは Azure のデータセンターの IP で、Node.js の fetch から自前の User-Agent を名乗って接続します。ブラウザではなく、データセンターから来て、TLS の指紋もブラウザと違うので、Bot Fight Mode が「自動化された通信」と判定する材料が揃っています。
監視スクリプトのほかにも、同じ条件に当てはまる自分の側の非ブラウザ通信を数えると次のとおりでした。
- GitHub Actions からの死活チェックと、OG 画像の文言ドリフト検知(本番の HTML を取りに行く)
- サンドボックスから CMS の API(
/_emdash/api/*)を叩く CLI。記事の投入・更新・タグ付けの経路 - Slack のイベント API からサイトへ届く POST(Slack から記事を投稿するプラグイン)
- 動作確認の
curl
Bot Fight Mode を入れると、これらの通信がすべてボットの判定の対象になります。
無料プランの Bot Fight Mode には例外が作れない
「監視の UA や IP だけ通せばよい」と考えて、WAF のカスタムルールで除外できないか調べました。Cloudflare のドキュメント(Bot Fight Mode のページ)にはっきり書いてありました。
Bot Fight Mode does not run on the Ruleset Engine — it operates in a separate evaluation pipeline where Skip, Bypass, and Allow actions have no effect.
WAF のカスタムルールは Ruleset Engine 上で動き、Bot Fight Mode はその外側で動くので、Skip も Allow も Bot Fight Mode には適用されません。Bot Fight Mode より先に評価されるのは IP Access Rules だけで、これは「IP Access Rule が先に一致すれば Bot Fight Mode は動かない」という形です。GitHub Actions のランナーの IP は広い範囲で変わるので、この方法は取れませんでした。
検索して出てくる「WAF のカスタムルールで Skip する」という手順は、Super Bot Fight Mode のものです。こちらは Pro 以上のプランに含まれていて、ドキュメントにも次のようにあります。
Custom rules are executed before Super Bot Fight Mode. To configure exceptions to Super Bot Fight Mode, create a custom rule with the Skip action.
つまり、例外を作りたければ Pro に上げる必要があります。Pro はゾーンごとの課金で、年払いなら月 20 ドル、月払いなら 25 ドルです。私は 8 ゾーン持っているので、Bot Fight Mode に例外を作る費用は月 160〜200 ドルになります。
無料プランの Bot Fight Mode でできるのは、有効か無効かの 2 択だけです。
8 月にもう一度考えて、入れないことにした
7 月に切ってから、8 月の下旬に D1 の読み取りと Workers の起動数を見直す作業があり(D1 の読み取りが 1 日 2,100 万行になった原因)、その中で「スキャナの通信を Worker の手前で落とせないか」と Bot Fight Mode を再検討しました。判断の材料はこうでした。
守りたい Workers の起動数と D1 の読み取りには、今は余裕がある。
- Workers の起動: 日平均 34,063 回(8 月 17〜19 日の 3 日間)。無料枠 10 万回/日の 34%
- D1 の読み取り: 1 日 152 万行。無料枠 500 万行の 30%
- CPU 上限による 503 は、原因だったテンプレート側の集計を取り除いて以降ゼロ(Workers 無料プランで 503 が出る原因は CPU 上限)
Bot Fight Mode で減らせる起動数も思ったより小さい。 スキャナは毎回違う URL を試すので、エッジキャッシュには当たりません。一方でそのアクセスは 404 を返すだけの軽いリクエストで、起動数の 34% の中に既に含まれています。切り分けの過程で「404 が主因」と一度は診断したのですが、実際の主因は通常ページのタクソノミー集計で、404 を減らしても数字は動きませんでした。
失うものは確実に発生する。 上に挙げた非ブラウザ通信が、どれも例外なしで判定の対象になります。しかも CLI や Slack の経路は使うときにしか通らないので、死活チェックのように毎日同じ経路で鳴って気づける失敗ではありません。「CLI が 403」「Slack から投稿できない」が別々の日にぽつぽつ起きて、そのたびに Bot Fight Mode のせいかどうかを切り分けることになります。無料プランの Security Events は保持が短く、Bot Fight Mode の判定理由は出ません。
今は Bot Fight Mode で減らしたい負荷が無く、止まるかもしれない通信を確かめる手段も無いので、入れないことにしました。
再訪する条件は、日次の Worker 起動が 7 万回に近づくか、閲覧者に見える 5xx が再び増えたときです。そのときも第一手は Bot Fight Mode ではなく Workers Paid(月 5 ドルで 1,000 万回/月)で、副作用がありません。
無料プランの Bot Fight Mode は自分の非ブラウザ通信まで止める
- 無料プランの Bot Fight Mode には WAF カスタムルールの Skip / Allow が適用されない。例外を作れるのは Pro 以上の Super Bot Fight Mode
- IP Access Rules だけは Bot Fight Mode より先に評価されるが、GitHub Actions のように IP が動く相手には使えない
- 有効にする前に、自分の非ブラウザ通信を数える。監視、CLI、Webhook の受信、確認の
curl。全部が判定の対象になる - 「入れて様子を見る」で 403 に気づけるのは、死活チェックのように毎日同じ経路を通るものだけ。たまにしか通らない経路の 403 は、原因の切り分けから始まる
- 守りたい枠が圧迫されていないなら、入れる理由が無い。圧迫されたときの第一手は課金の方が確実で、副作用が無い
7 月の 403 は 11 分で直りましたが、それは監視の初回実行で、しかも自分がその場で見ていたからです。同じことが CLI で起きていたら、もっと時間がかかっていたと思います。