docs: add Cloudflare & anti-DDoS setup guide to README
CI / check (push) Successful in 51s
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / tests-ui (push) Failing after 32s
CI / tests-unit (push) Successful in 1m37s
CI / tests-integration (push) Successful in 1m41s
CI / preflight (push) Skipped
CI / deploy (push) Skipped

This commit is contained in:
openhands committed 2026-09-22 23:38:54 +02:00
1 parent c56696b230
commit 64edb81ab7
1 file changed
+88
+88
View File
@@ -687,6 +687,94 @@ The CMS caches through `cached()` / `cachedQuery()` (`src/lib/cache.ts`): an in-
---
## Cloudflare & Anti-DDoS Protection
Two layers work together to keep the origin alive during misuse, floods, and
repeat-offender attacks:
1. **App-layer gate** (production only) — per-IP request buckets per category
(pages / API / auth), a whole-site safety valve, and escalation tiers that
temporarily hard-block offending IPs (`src/lib/ddos-guard.ts`).
`/api/health` is always exempt so the Docker liveness probe never trips it.
2. **Automatic Cloudflare edge blocks** (optional, works on every Cloudflare
plan including Free) — when the gate hard-blocks an IP, the CMS mirrors the
block to the zone's **IP Access Rules** (`src/lib/cloudflare-api.ts`), so a
repeat offender is dropped at the Cloudflare edge before it reaches your
server. Blocks expire together with the gate's tiered block (a 30s sweep
removes them; see `src/instrumentation.ts`).
### Prerequisites
- The domain must actually be **proxied through Cloudflare** (orange cloud) —
automatic edge rules can only stop traffic that transits the edge. Restrict
direct access to the origin ports (e.g. only allow your server IP(s),
Cloudflare IPs, or swap the firewall to 443/80 only) so attackers cannot
bypass Cloudflare.
- A Cloudflare API token with the following zone permissions:
- `Zone > Zone > Read`
- `Zone > Firewall > Edit`
Create one at **My Profile → API Tokens → Create Token** (custom token).
### 1. Environment variables
`.env.example` documents every anti-DDoS / Cloudflare variable. In your real
`.env`:
```dotenv
# --- App-layer gate (production only) ---
ANTI_DDOS_ENABLED=true
ANTI_DDOS_PAGES_LIMIT=300
ANTI_DDOS_PAGES_WINDOW_SEC=60
ANTI_DDOS_API_LIMIT=600
ANTI_DDOS_API_WINDOW_SEC=60
ANTI_DDOS_AUTH_LIMIT=20
ANTI_DDOS_AUTH_WINDOW_SEC=60
ANTI_DDOS_GLOBAL_LIMIT=18000
ANTI_DDOS_GLOBAL_WINDOW_SEC=60
ANTI_DDOS_GLOBAL_HALT_MS=10000
ANTI_DDOS_VIOLATION_WINDOW_SEC=600
ANTI_DDOS_MAX_VIOLATIONS=10
ANTI_DDOS_BLOCK_TIERS=5:600,20:3600,50:86400
# --- Cloudflare API (automatic edge blocks) ---
CLOUDFLARE_API_TOKEN=<your API token>
CLOUDFLARE_ZONE_ID=<your zone id — Dashboard → your domain → Overview>
CLOUDFLARE_AUTO_BLOCK_ENABLED=true
```
Credentials live in env only and are never stored in Redis-visible config.
Restart the container after changing them.
### 2. Verify in the admin panel
Open **DevOps → Anti-DDoS protection**
(`/admin/devops/antiddos`, requires `settings.view`) and:
- Check the **Cloudflare** card shows the request arrived proxied, and the
green **API configured** badge.
- Click **Verify connection** — it confirms the token can read the zone and
shows the zone name.
- Keep the **Automatically create Cloudflare edge blocks** toggle on.
- The **Cloudflare edge blocks** card lists every auto-created rule with its
remaining TTL; **Remove rule** deletes it — and **Unban** lifts both the
host-block and the edge rule in one action.
### 3. Notes
- **Only proxied traffic** is edge-blocked. The gate verifies a request
actually transited Cloudflare (`CF-Ray` / `CDN-Loop`); a spoofable
`CF-Connecting-IP` alone is never trusted.
- **Free plan**: IP Access Rules support is plan-independent; the limit is
~300 rules per zone and the 30s sweep keeps expired rules cleaned up.
- **Distributed botnets**: many unique IPs each under the threshold are shed
by the whole-site valve (short global halt) rather than banned one-by-one,
which avoids collateral damage to shared IPs.
- **Volumetric L3/L4 floods** cannot be stopped by application code — for
those, keep the domain on Cloudflare's orange cloud so Cloudflare's own
always-on DDoS protection absorbs them.
---
## Production Deployment (PM2)
```bash