# Vercel Firewall × WAF × BotID implementation guide: protect the entrance with DDoS mitigation, custom rules, rate limiting, and an invisible CAPTCHA

> A platform-layer security implementation guide faithful to Vercel's official docs. With real code, it explains automatic DDoS mitigation, Vercel WAF custom rules (allow/deny/challenge/log/rate limit), IP blocking and managed rule sets, 300ms global propagation and instant rollback, BotID (invisible CAPTCHA, Basic/Deep Analysis) client/server instrumentation, and Attack Challenge Mode.

- Published: 2026-06-28
- Author: 友田 陽大
- Tags: Vercel, セキュリティ, WAF, DDoS, Next.js, 可観測性, B2B SaaS
- URL: https://tomodahinata.com/en/blog/vercel-firewall-waf-botid-ddos-security-guide
- Category: Vercel in production
- Pillar guide: https://tomodahinata.com/en/blog/vercel-production-platform-guide

## Key points

- Vercel's platform-layer security is three layers: 'DDoS mitigation (automatic on all plans) + WAF (custom rules/IP block/managed rule sets) + BotID (invisible CAPTCHA).' Make it effective at the entrance before writing app code.
- WAF custom rules tie allow/deny/challenge/log/rate-limit actions to conditions (path, IP, region, header, JA4, etc.). Setting changes propagate globally in 300ms, and on a problem you can instantly roll back from the audit log. Hobby 3 rules/3 IPs, Pro 40 rules/100 IPs, Ent 1000/1000.
- BotID is an invisible CAPTCHA (Kasada). It blocks advanced bots like Playwright/Puppeteer from checkout, signup, and APIs without user interaction. Basic is free on all plans; Deep Analysis is Pro $1/1000 checkBotId() calls.
- BotID calls initBotId on the client and checkBotId() on the server, branching on the result. Deep Analysis billing occurs only when you call checkBotId().
- WAF is one layer of defense in depth. App-layer authorization, RLS, and injection defense (IDOR/SQLi/XSS/SSRF) are separate and can't be protected by a WAF. Design both wheels.

---

Security is the area where it's most cost-efficient to make it **effective at the entrance**, "before writing app code." Vercel has three layers at the platform level — **DDoS mitigation, WAF, BotID** — letting you trim the attack surface with minimal code changes. This article summarizes how to defend the entrance with real code, faithful to the official specs of [Vercel Firewall](https://vercel.com/docs/vercel-firewall), [Vercel WAF](https://vercel.com/docs/vercel-firewall/vercel-waf), and [BotID](https://vercel.com/docs/botid).

For the full picture, see the [Vercel production-operations guide](/blog/vercel-production-platform-guide).

> **An important premise**: this article is about **platform-layer (entrance)** security. App-layer **authorization, RLS, and injection defense (IDOR/SQLi/XSS/SSRF)** can't be protected by a WAF. They're separate layers, designed as both wheels. For the app layer, see [Next.js × Supabase application-layer security](/blog/nextjs-supabase-application-security-guide).

---

## The role division of the three layers

| Layer | What it protects | Plan |
|---|---|---|
| **DDoS mitigation** | Exhaustion attacks via mass requests | All plans (automatic, free) |
| **WAF** | Blocking/challenging/rate-limiting/IP-blocking malicious requests | Hobby/Pro/Enterprise (scale differs) |
| **BotID** | Advanced bots posing as humans (scraping, credential stuffing) | Basic free on all plans / Deep Analysis Pro·Ent |

On the foundation of "DDoS mitigation that works broadly and automatically," layer "WAF that controls narrowly with rules" and "BotID that distinguishes human/bot" — this is the entrance's defense in depth.

---

## DDoS mitigation: automatic on all plans

Vercel's DDoS mitigation works **automatically on all plans.** With no configuration, it absorbs mass L3/L4/L7 requests. When an attack is severe, use the **Attack Challenge Mode** described below alongside. This is a state where "the foundation is protected even without doing anything," and you add **intentional control** here with WAF and BotID.

---

## Vercel WAF: entrance rules that propagate in 300ms

Vercel WAF is a mechanism that monitors and controls traffic to the site by **logging, blocking, and challenging.** Its biggest feature is operability — **setting changes propagate globally in 300ms**, and on a problem you can **instantly roll back from the audit log** ([WAF docs](https://vercel.com/docs/vercel-firewall/vercel-waf)).

### Three control means

| Means | Content |
|---|---|
| **IP block** | Block specific IPs/CIDRs |
| **Custom rules** | Declarative condition → action rules |
| **Managed rule sets** | Off-the-shelf rule groups like OWASP (Enterprise) |

### Limits by plan

| Feature | Hobby | Pro | Enterprise |
|---|---|---|---|
| Project IP blocks | Up to 3 | Up to 100 | Up to 1000 |
| Custom rules | Up to 3 | Up to 40 | Up to 1000 |
| Managed rule sets | — | — | Yes |

### How to think about custom rules

A custom rule ties an "**action**" to a "**condition** (path, IP, region, request header, User-Agent, JA4, etc.)." The actions are —

- **Allow**: let through (allowlist)
- **Deny**: block
- **Challenge**: challenge (verify human)
- **Log**: record only (observe first, then tighten)
- **Rate Limit**: limit by count per period

The iron rule in practice is **"observe with Log first → confirm the impact → escalate to Deny/Challenge."** Avoid the incident of jumping straight to Deny and catching legitimate users. With 300ms propagation and instant rollback, this "observe then tighten" operation runs safely.

### Rate limiting: stop abuse structurally

For **high-value routes prone to abuse** like login and search, use WAF rate limiting to Challenge/Deny when "N times/minute from the same IP" is exceeded.

> **Relationship with Fluid Compute**: in-app rate limiting (a memory counter) doesn't work correctly on serverless (since instances are shared/distributed). Use **WAF rate limiting** that works at the entrance, or [distributed rate limiting with Upstash (Redis)](/blog/nextjs-serverless-rate-limiting-vercel-guide). WAF rate limiting works **before the function is called**, so it also prevents Active CPU billing.

### Configuration and rollback (the operations flow)

```
ダッシュボード → プロジェクト → Firewall
  ① Custom Rule を Log で追加 → トラフィックを観測
  ② 影響を確認したら Deny / Challenge / Rate Limit へ昇格（300msで反映）
  ③ 誤遮断が出たら View Audit Log → 過去構成へ Restore（即時ロールバック）
```

You can also **stage** rules in the CLI and apply them in bulk (`vercel firewall`). Suited to teams that want to review and apply rules in an Infrastructure-as-Code manner.

### Attack Challenge Mode: the emergency valve during an attack

When an attack is severe, turning **Attack Challenge Mode** ON imposes a temporary challenge on all visitors and greatly reduces automated traffic. Legitimate users get a minor confirmation, and you turn it OFF once the attack subsides. A two-stage setup: "**precise rules in peacetime, one mode toggle in an emergency.**"

---

## BotID: invisible CAPTCHA

A "pick the images" UI like reCAPTCHA hurts UX while getting broken by advanced bots. **BotID** is an **invisible CAPTCHA** based on Kasada — without user interaction, it detects bots "posing as humans" like Playwright/Puppeteer ([BotID docs](https://vercel.com/docs/botid)).

### Two levels and pricing

| Level | Content | Plan | Pricing |
|---|---|---|---|
| **Basic** | Verifies the integrity of the challenge response. Catches many simple bots | All plans | Free |
| **Deep Analysis** | Analyzes thousands of client signals with ML. Handles the most advanced bots | Pro / Enterprise | Pro: **$1 / 1000 `checkBotId()` calls**, Ent: custom |

> **Billing originates from the `checkBotId()` call.** Passive page views aren't billed. So **calling `checkBotId()` only on high-value routes** is cost-optimal.

### Instrumentation: two points, client + server

BotID is two-point instrumentation: "set up the challenge on the client, verify on the server."

```ts
// ① クライアント側：保護したいルートのチャレンジを初期化
// app/providers.tsx ("use client")
"use client";
import { initBotId } from "botid/client";

initBotId({
  protect: [
    { path: "/api/checkout", method: "POST" },
    { path: "/api/signup", method: "POST" },
  ],
});
```

```ts
// ② サーバー側：高価値エンドポイントで検証して分岐
// app/api/checkout/route.ts
import { checkBotId } from "botid/server";

export async function POST(request: Request) {
  const verification = await checkBotId(); // ← Deep Analysis課金はここで発生

  if (verification.isBot) {
    return new Response("Forbidden", { status: 403 });
  }

  // 人間と判定された場合のみ本処理へ
  return handleCheckout(request);
}
```

`checkBotId()` runs Deep Analysis (when configured) after passing Basic verification and returns the result (`isBot`, etc.). Put it in only on routes where "being trashed by bots causes damage," like **checkout, signup, inquiries, and inventory APIs.**

### Verified bots, bypass, and observability

- **Legitimate bots** like search engines can be let through with an allowlist (Verified Bots).
- On a false positive, you can let them through with a **WAF bypass rule** that detours the BotID judgment.
- BotID checks can be visualized in the **Firewall tab's traffic filter** or Observability Plus.

---

## Environment variables and OIDC: don't put secrets at the entrance

As important as entrance defense is "not leaking secrets to code/clients."

- Put API keys and DB connection strings into **environment variables.** The `NEXT_PUBLIC_` prefix is **exposed to the browser**, so don't attach it to secrets ([env-leak prevention](/blog/nextjs-env-secret-leak-prevention-public-vars-guide)).
- Don't place long-lived access keys for external clouds; obtain temporary credentials **keylessly via OIDC.**
- On Fluid Compute, **don't put user-specific data in global state** (preventing tenant crossing; [Functions guide](/blog/vercel-functions-fluid-compute-streaming-cron-guide)).

---

## Production checklist (entrance security)

- [ ] DDoS mitigation is working (automatic). Share the **Attack Challenge Mode** procedure for attacks
- [ ] Introduce WAF custom rules in the order **observe with Log → escalate**
- [ ] **WAF rate limiting** on high-value routes (stop before the function is called)
- [ ] Confirm the **audit log → instant rollback** procedure for false blocks
- [ ] **BotID** on checkout/signup, etc., with `checkBotId()` limited to the target routes
- [ ] Allow legitimate bots, rescue false positives with WAF bypass
- [ ] Secrets in environment variables, no secrets in `NEXT_PUBLIC_`, external via OIDC
- [ ] **App-layer security (authorization/RLS/injection) designed separately** (a WAF can't protect it)

---

## Summary

Vercel's platform-layer security is defense in depth of **"broad automatic DDoS mitigation" + "narrow precise WAF" + "BotID that distinguishes human/bot."**

1. **DDoS mitigation is the foundation** (automatic on all plans)
2. **WAF tightens safely via Log→escalate**, operated with 300ms propagation and instant rollback
3. **Rate limiting at the entrance** (also protects Active CPU)
4. **Limit BotID to high-value routes** and narrow the billing-originating `checkBotId()`
5. **WAF doesn't replace app-layer authorization/injection defense** — both wheels

Next, head to the [storage, Blob, and Edge Config guide](/blog/vercel-storage-blob-edge-config-marketplace-guide) for placing state.

> This article is based on the [Vercel Firewall](https://vercel.com/docs/vercel-firewall) / [Vercel WAF](https://vercel.com/docs/vercel-firewall/vercel-waf) / [BotID](https://vercel.com/docs/botid) official documentation (as of June 2026). Specs, limits, and pricing are updated, so confirm the latest values officially at production adoption.
