6 · Cloudflare CDN
Objective — front the app with Cloudflare on the Free plan (TLS termination, DDoS absorption, a WAF, global caching): nameserver cutover, API-driven DNS, Full (Strict) SSL + HSTS, WAF and rate limiting, cache rules, DNSSEC, and a final API verification probe — with Rocket Loader OFF for Laravel.
Steps at a glance:
- Budget against the Free plan — Free tier limits are real — plan inside them so nothing silently fails to apply.
- Add the zone and cut over nameservers — add the zone and switch nameservers, so Cloudflare can proxy traffic before security and cache rules are configured.
- Configure DNS via the API — Drive record changes through the API so they’re scriptable and auditable. Use a scoped token (Zone:DNS:Edit), never the global key.
- Set SSL/TLS and HSTS — set Full Strict TLS and HTTPS enforcement, so browser-to-edge and edge-to-origin traffic are both protected.
- Configure security: WAF + rate limiting — Spend the 5 WAF rules on real threats (block bad bots, restrict
/adminby country/ASN, challenge suspicious paths). - Configure caching and speed — cache only safe static assets and enable speed features, so performance improves without breaking authenticated Laravel routes.
- Enable DNSSEC and Zero Trust — turn on DNSSEC and Zero Trust where appropriate, so DNS tampering risk drops and protected admin paths have an extra access layer.
- Run the final verification probe — Confirm the edge is live, encrypted end-to-end, and DNSSEC-validated.
Background
Section titled “Background”1. Budget against the Free plan
Section titled “1. Budget against the Free plan”Free tier limits are real — plan inside them so nothing silently fails to apply.
| Resource | Free-plan budget | Notes |
|---|---|---|
| WAF custom rules | 5 | Spend on the highest-value blocks |
| Rate-limiting rules | 1 | Reserve for login/API abuse |
| Cache rules | a few | One good static-asset rule goes far |
| Page Rules (legacy) | 3 | Prefer modern Rules where possible |
2. Add the zone and cut over nameservers
Section titled “2. Add the zone and cut over nameservers”-
Add the site as a zone in Cloudflare; it imports existing records — audit the import against what page 2 and page 5 created.
- ✅ The zone exists and its imported records have been audited.
-
Update the registrar’s nameservers to Cloudflare’s pair, then wait for the zone to go Active.
Terminal window dig NS example.com # returns Cloudflare nameservers once Active# Expected: the NS query returns Cloudflare's nameserver pair- ✅
dig NS example.comreturns Cloudflare and the zone shows Active.
- ✅
3. Configure DNS via the API
Section titled “3. Configure DNS via the API”Drive record changes through the API so they’re scriptable and auditable. Use a scoped token (Zone:DNS:Edit), never the global key. Before mutating, read the current record and save its ID/content/proxied value so rollback is one PATCH away.
-
Upsert records through the API — load the token from vault or a mode-600 file; never
export CF_API_TOKEN="<literal>"in a runbook (visible in shell history andps):Terminal window # CF_API_TOKEN and ZONE_ID already exported from vault / ~/.zshrc (see Phase 1 machine setup)RECORD_NAME="app.example.com"ORIGIN_IP="<origin-ip-from-Zaj-PROJECT>"EXISTING_ID="$(curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=A&name=$RECORD_NAME" \-H "Authorization: Bearer $CF_API_TOKEN" | jq -r '.result[0].id // empty')"if [ -n "$EXISTING_ID" ]; thenMETHOD="PATCH"; URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$EXISTING_ID"elseMETHOD="POST"; URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records"ficurl -s -X "$METHOD" "$URL" \-H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \--data "{\"type\":\"A\",\"name\":\"$RECORD_NAME\",\"content\":\"$ORIGIN_IP\",\"proxied\":true}"# Expected: a JSON response with "success":true and the created/updated record- ✅ Records are created/updated via the scoped-token API;
proxied:true(orange cloud) is used for production only after the stale-DNS check passes and origin TLS exists.false(grey cloud) is DNS-only and is the safe temporary state for certificate issuance.
- ✅ Records are created/updated via the scoped-token API;
4. Set SSL/TLS and HSTS
Section titled “4. Set SSL/TLS and HSTS”- Set SSL mode to Full (Strict) — encrypts edge↔origin and validates the origin cert.
Flexiblecauses redirect loops and is insecure. - Enable Always Use HTTPS.
- Enable HSTS only once you’re confident HTTPS is permanent (it’s hard to undo):
max-age≥ 6 months, include subdomains, preload optional.
5. Configure security: WAF + rate limiting
Section titled “5. Configure security: WAF + rate limiting”Spend the 5 WAF rules on real threats (block bad bots, restrict /admin by country/ASN, challenge suspicious paths). Use the single rate-limit rule on the login or API endpoint most likely to be brute-forced.
Load feedback_api_scope_verification.md before labeling a Cloudflare security action as dashboard-only. Many Cloudflare failures are token-scope issues, not missing API support; probe first and record whether the action is API-automatable, token-scope-dependent, or truly browser-gated.
6. Configure caching and speed
Section titled “6. Configure caching and speed”- Cache rule: cache static assets (
/build/*,/images/*) aggressively; bypass cache for authenticated/app routes and anything with a session cookie. - Enable Brotli and Tiered Cache. (Cloudflare removed Auto Minify in Aug 2024 — minify in your build pipeline instead.)
7. Enable DNSSEC and Zero Trust
Section titled “7. Enable DNSSEC and Zero Trust”- Enable DNSSEC in Cloudflare, then add the DS record it generates at your registrar — DNSSEC isn’t active until the registrar side is done.
- Optionally protect staging with Zero Trust Access (email-OTP gate) so the unfinished site isn’t public.
-
Publish the DS record in order.
Terminal window DOMAIN="example.com"# 1) Cloudflare dashboard/API: enable DNSSEC and copy Key Tag, Algorithm, Digest Type, Digest.# 2) Registrar: publish exactly those DS values.dig +short DS "$DOMAIN" @1.1.1.1# Expected: one DS row after registrar publication; before publication this can be empty- ✅ Cloudflare DNSSEC is enabled and the registrar publishes the matching DS row.
8. Run the final verification probe
Section titled “8. Run the final verification probe”Confirm the edge is live, encrypted end-to-end, and DNSSEC-validated.
-
Probe the zone over HTTP and the API.
Terminal window # Zone is active and proxied (expect cf-ray + cf-cache-status headers)curl -sI https://app.example.com | grep -iE 'cf-ray|cf-cache-status|strict-transport'# SSL mode end-to-end (expect HTTP/2 200, valid cert)curl -sI https://app.example.com | head -1# DNSSEC DS published at the registrardig +short DS example.com @1.1.1.1# DNSSEC validated via a validating resolver (AD bit)dig +dnssec @1.1.1.1 example.com A | grep -q 'flags:.* ad' && echo "DNSSEC validated"dig DNSKEY example.com +short | grep -q . && echo "DNSKEY published"# Zone status via APIcurl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID" \-H "Authorization: Bearer $CF_API_TOKEN" | grep -o '"status":"[^"]*"'# Expected: cf-ray present (proxied), a strict-transport-security header, DS row present,# zone "status":"active", and DNSSEC validated- ✅ The probe shows
cf-ray(proxied), thestrict-transport-securityheader (HSTS), zone"status":"active", and DNSSEC validated.
- ✅ The probe shows
Checklist
Section titled “Checklist”Do not mark this step done until every box below is checked.
- 👤 Zone Active — nameservers are Cloudflare’s; imported records audited (esp. email DNS).
- 🔀 Production origin safe — production A record resolves to the current origin and origin TLS is valid before orange-cloud proxying.
- 👤 SSL hardened — SSL = Full (Strict), Always Use HTTPS on, HSTS set.
- 👤 WAF within budget — WAF rules + 1 rate-limit rule within Free-plan budget.
- 🔀 Cache rule live — static-asset cache rule live; app/auth routes bypass cache.
- 🔀 Rocket Loader OFF; DNSSEC active — DS added at registrar and
dig +short DS <domain> @1.1.1.1returns the Cloudflare values. - 🤖 Probe green — verification probe shows
cf-ray, HSTS header, and"status":"active".