Cloudflare Edge (Routing + WAF)

Cloudflare sits in front of the whole platform for security (WAF, TLS, bot/DDoS mitigation, cache) and as the request router. The coexistence of the Website and WordPress on the same hostname swapps.com is resolved with Workers Routes in the swapps.com zone.

Full zone reference

The detailed zone configuration — DNS (66 records), SSL/TLS and certificates, all security rules (rate limiting, custom and managed WAF rulesets, config rules), cache, page rules, the 48 Workers Routes and recommendations — is consolidated in the repo's CLOUDFLARE.md. This page summarizes the essentials; to audit or change something, go to that document.

Zone at a glance

Aspect Current value
Zone / plan swapps.com · Pro · 11f7934f8dcfecb73fa95691f02b1dd1
Account Swapps · b7edd98532935f2d2477d46a266cd1bc
DNS 66 records (8 A, 10 AAAA, 19 CNAME, 10 MX, 18 TXT, 1 CAA); email on Google Workspace + Mailgun + SendGrid
TLS / SSL Min TLS 1.0 · mode Full · HSTS injected by the Worker (not at zone level)
Certificates Universal Google Trust Services (active) + Sectigo (backup)
Rate limiting Login 15/60s and General 120/60s (excludes static assets) — Challenge action
WAF managed Cloudflare Managed Ruleset + OWASP Core Ruleset deployed
IP Access Rules None

Recommended pending items

See CLOUDFLARE.md §7 for detail/priority: raise Min TLS to 1.2, evaluate SSL Full (Strict), enable HSTS at the zone level, and review the "Allow scraping" config rule that leaves a fixed IP with security nearly disabled.


Routing mechanism

There is no "WordPress worker". The swapps-client Worker captures all of swapps.com via a wildcard route (swapps.com/*) plus explicit per-section routes. The routes WordPress must serve are marked "Workers disabled": on those the Worker does not run and Cloudflare serves the request directly from the origin (WordPress on Pantheon). In other words, WordPress is the origin of the domain and the Worker only intercepts the React site's routes.

Routes that run the swapps-client Worker (Website, React SSR)

Route Notes
swapps.com and swapps.com/* Wildcard: captures the whole site
swapps.com/assets/* React site assets
swapps.com/services* Services section
swapps.com/case-studies*, .../case-studies/sector* Case studies
swapps.com/get-swapps* Get Swapps landing
swapps.com/about-us* About us
swapps.com/blog* Blog (SSR render; content comes from the worker → WP REST API)
swapps.com/es* Spanish site
swapps.com/style-guide* Style guide

Routes that run the swapps-ai Worker (AI)

Route Notes
swapps.com/api/ai/* Plan recommendation and refinement endpoint, served by Cloudflare Workers AI.

Routes with "Workers disabled" → served by the origin (WordPress / Pantheon)

Route Notes
swapps.com/wp-content/* CMS media and assets
swapps.com/wp-includes/* WordPress core
swapps.com/wp-admin/* CMS backoffice
swapps.com/wp-login.php* WordPress login
swapps.com/wp-json/* WP REST API (consumed by the Platform worker)
swapps.com/wp-cron.php WordPress cron
swapps.com/health Origin health check
swapps.com/robots.txt robots.txt served by WordPress

Mental rule

On swapps.com, WordPress routes (/wp-*, plus /health and /robots.txt) are "Workers disabled" and fall to the origin; the Worker swapps-client serves the rest. When adding a new site section, the wildcard usually suffices; when exposing something new from WordPress, you must add its route with "Workers disabled".

Other hostnames don't use these routes: app.swapps.com resolves to the App (Django, K8s/Argo CD ingress) and swapps-worker.swapps.workers.dev is the Platform worker's own workers.dev domain.


WAF exceptions for server-to-server traffic

Cloudflare applies a Managed Challenge (Super Bot Fight Mode) to traffic that looks automated. A browser solves the JS challenge and passes; a server-to-server client (an inbound webhook, another server, a Worker) cannot solve it and gets HTTP 403 with the cf-mitigated: challenge header. So each server-to-server ingress needs a WAF exception (Skip action) scoped by route.

Gotcha: Super Bot Fight Mode runs in its own phase

Super Bot Fight Mode runs in the http_request_sbfm phase, distinct from WAF Managed Rules (http_request_firewall_managed). Marking Skip → "All managed rules" does not disable the bot challenge; you must mark the Super Bot Fight Mode component.

Current rules (WAF → Custom rules):

Zone Inbound traffic Match (summary) Skip
app.swapps.com ClickUp / Stripe / Zapier webhooks POST and path starts with /services/webhooks/clickUp, /subscriptions/webhooks/stripe or /zapier/webhooks Super Bot Fight Mode
swapps.com Platform worker → WP REST API path starts with /wp-json/wp/v2/ and cf.worker.upstream_zone ne "" bot challenge
swapps.com API requests to AI path starts with /api/ai/ Super Bot Fight Mode

Reference expression for the app.swapps.com rule:

(http.request.method eq "POST" and (
  starts_with(http.request.uri.path, "/services/webhooks/clickUp") or
  starts_with(http.request.uri.path, "/subscriptions/webhooks/stripe") or
  starts_with(http.request.uri.path, "/zapier/webhooks")
))

Match criteria and scope:

  • External webhooks → by path + POST method. They are not Workers, so cf.worker.upstream_zone doesn't apply; each endpoint validates its own signature/token (ClickUp X-Signature, Stripe STRIPE_WEBHOOK_SECRET, Zapier token), so the skip only removes the bot challenge, not authentication. The path is case-sensitive (e.g. clickUp with a capital U): it must match what each provider has registered.
  • Worker → WordPress → by path + cf.worker.upstream_zone (only Worker subrequests from the account); it does not open /wp-json to external traffic.
  • The skip is deliberately narrow: broad routes like app.swapps.com/api/* or the rest of the site remain challenged. Only specific server-to-server ingress routes are excepted.

When adding a new inbound integration

When adding a new inbound integration (a webhook from another provider, or another app that consumes the API server-to-server), you must add its route to the corresponding Skip rule; otherwise it will fail with a silent 403 the app never sees.