Skip to main content

Rate Limiting Architecture

How EdgeBase protects against abuse with a 2-layer rate limiting system, auth-specific defenses, and a WebSocket DDoS gate.

Mental Model

Think of EdgeBase rate limiting as two different tools working together:

ToolRoleGood ForNot Good For
Software counterApp-level soft limitAbuse protection, traffic shaping, per-group tuningExact global quotas or billing
Cloudflare bindingEdge safety netCatching overflow that slips past per-isolate memoryPrecise cross-region accounting

If you need strict global counting, use a central state system such as a Durable Object-based limiter.

2-Layer Architecture

Every HTTP request passes through two independent rate limiting layers before reaching any Durable Object:

Request


Layer 1: Software Counter (per-isolate memory)
│ ├─ Config-driven limits per group
│ └─ Primary defense line


Layer 2: Cloudflare Rate Limiting Binding
│ ├─ Deploy-time edge ceiling for built-in groups
│ └─ Safety net for isolate restarts and distributed traffic


Durable Object (only reached if both layers pass)

Layer 1 — Software Counter

The primary defense. A per-isolate Fixed Window Counter in memory, configured through edgebase.config.ts:

GroupDefault LimitKeyWhat It Protects
global10,000,000 / 60sIPOverall request ceiling
db100 / 60sIPDatabase CRUD operations
storage50 / 60sIPFile upload/download
functions50 / 60sIPApp Functions execution
auth30 / 60sIPAll auth endpoints
authSignin10 / 60semailSign-in brute force protection
authSignup10 / 60sIPSign-up spam prevention
events100 / 60sIPRealtime event endpoints

Most groups use the client's IP address as the rate limit key because the middleware runs before authentication (no auth.id is available yet). The authSignin group is the exception — it uses the email address to prevent credential stuffing against specific accounts.

When a limit is exceeded, the server returns 429 Too Many Requests with a Retry-After header.

Layer 2 — Binding Ceiling

The safety net. By default, EdgeBase synthesizes Cloudflare Rate Limiting Bindings for the built-in groups at 10,000,000 requests per 60 seconds, and you can override those binding values from config. This layer catches requests that slip through when:

  • The Worker isolate restarts (resetting in-memory counters)
  • Traffic is spread across many isolates or edge instances
  • Memory pressure causes counter eviction

Miniflare emulates these bindings in Docker and local development environments, so the same architecture works in dev as well.

Configuration

Customize rate limits in edgebase.config.ts:

export default defineConfig({
rateLimiting: {
db: { requests: 200, window: '60s' },
storage: { requests: 100, window: '60s' },
auth: { requests: 60, window: '60s' },
authSignin: { requests: 20, window: '5m' },
authSignup: { requests: 5, window: '60s' },
},
});

Only the groups you specify are overridden — all others keep their defaults. The global group is intentionally set high (10M/60s) and is rarely customized.

For built-in groups, you can also add binding settings if you want the generated Cloudflare binding to match your app policy more closely. Custom groups remain software-counter only.

Auth-Specific 3-Layer Defense

Authentication endpoints receive additional protection through 3 specialized rate limit groups, all of which must pass before the request reaches the auth handler:

Auth request (e.g., POST /auth/signin)


Group 1: auth (IP-based, 30/60s)
│ └─ Blocks IP-level flooding of all auth endpoints


Group 2: authSignin (email-based, 10/60s)
│ └─ Blocks brute-force against a specific account


Group 3: authSignup (IP-based, 10/60s)
│ └─ Blocks mass account creation from a single IP


Captcha (if enabled)


Auth Handler (D1)

This layered approach means:

  • A general flood is caught by the auth group
  • Targeted credential stuffing is caught by authSignin (email-keyed)
  • Account creation spam is caught by authSignup
  • Bot traffic is caught by Captcha (Cloudflare Turnstile)

All three groups plus the global group must pass — failing any single one returns 429.

This Is Still Soft Protection

Even with multiple layers, this architecture is for abuse mitigation, not exact quota accounting. It is appropriate for protecting endpoints, but not for billing or hard tenant quotas.

WebSocket DDoS Gate

WebSocket connections present a unique challenge: the upgrade happens before authentication, so an attacker could open thousands of unauthenticated connections to overwhelm the Realtime DO.

EdgeBase prevents this with a KV-based pending connection counter at the Worker level:

WebSocket upgrade request


KV: ws:pending:{ip}

├─ Count < 5 → Increment counter, allow upgrade
│ │
│ ▼
│ Realtime DO
│ │
│ ├─ Auth succeeds → Decrement counter
│ └─ Auth fails/timeout → Connection closed
│ (counter auto-expires)

└─ Count >= 5 → 429 Too Many Requests (upgrade rejected)
ParameterValue
Max pending connections per IP5
KV keyws:pending:{ip}
TTL10 seconds

Why KV Instead of Rate Limiting Binding?

The Rate Limiting Binding's limit() API is a one-way counter (increment only). WebSocket pending tracking requires a bidirectional counter — increment on connection, decrement on successful auth. KV allows direct read/write of the counter value.

Self-Healing

The 10-second TTL on the KV key means the counter automatically resets even if the decrement fails (server crash, network error). No cleanup logic is needed — stale counters simply expire.

Service Key Rate Limit Policy

Requests authenticated with a Service Key bypass EdgeBase's app-level rate limits entirely (global, db, storage, functions, auth).

Rate Limit GroupNormal RequestService Key Request
globalAppliedBypassed
dbAppliedBypassed
storageAppliedBypassed
functionsAppliedBypassed
authAppliedBypassed
authSigninAppliedBypassed
authSignupAppliedBypassed
eventsAppliedBypassed

This policy allows server-to-server operations (bulk migrations, admin scripts, automated backups) to run without hitting rate limits designed for end-user traffic.

How It All Fits Together

                    ┌─────────── HTTP Request ───────────┐
│ │
▼ │
Global Rate Limit (IP, 10M/60s) │
│ │
▼ │
Group Rate Limit (db/storage/auth/etc.) │
│ │
▼ │
[Auth endpoints only] │
Auth-specific groups (auth + authSignin/Signup) │
│ │
▼ │
[Auth endpoints only] │
Captcha (Cloudflare Turnstile) │
│ │
▼ │
JWT Verification (local crypto) │
│ │
▼ │
Access Rules │
│ │
▼ │
Durable Object │
│ │
└──────── Response ──────────────────┘


┌─────── WebSocket Upgrade ──────────┐
│ │
▼ │
Global Rate Limit (IP, 10M/60s) │
│ │
▼ │
WS DDoS Gate (KV, 5 pending/IP) │
│ │
▼ │
Realtime DO │
│ │
├─ Auth message (5s timeout) │
├─ Subscribe/Presence/Broadcast │
│ │
└──────── WebSocket ─────────────────┘

Next Steps