Fast, safe SSL & security on the edge
Once the domain is on Cloudflare, the next job is to make it fast and safe by default. The good news: the whole security baseline below is free, and it's layered — every request from the internet is checked at Cloudflare's edge, one layer at a time, before it ever reaches your site. This is Part 4 of the deploy series, and it's all about the security layers.
Think of it as a stack of gates. A request falls down through each one — forced onto HTTPS, encrypted, screened for attacks and bots, and finally handed your security headers — and only a clean request reaches the page.
Below the last gate sits your site & form. Let's walk down the stack.
HTTPS & TLS — encrypt everything
TLS (Transport Layer Security, the modern name for SSL) is the encryption behind the padlock — it scrambles traffic so nobody between the visitor and the site can read or tamper with it. Three free switches do the heavy lifting:
- Always Use HTTPS — if anyone reaches
http://pangaea.id, Cloudflare redirects them to thehttps://version. The insecure door is closed; visitors only ever travel encrypted. - TLS mode → Full (strict) — this controls how Cloudflare talks to your server (the origin). "Full (strict)" encrypts both legs — browser↔edge and edge↔origin — and checks that the origin's certificate is real and trusted. The weaker modes ("Flexible", "Full") leave the second leg unencrypted or unverified; don't use them.
- Minimum TLS 1.2, and enable TLS 1.3 — refuse the old, broken versions of the protocol and prefer the newest, fastest one.
HSTS — "only ever reach me over HTTPS"
HSTS (HTTP Strict Transport Security) is a header that tells the browser one thing: only ever
reach me over HTTPS, even if someone types http:// or clicks an old link. The browser remembers
it and upgrades every future request automatically — there's no insecure first hop to intercept.
It's a single header on the /* block:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age=31536000 is one year (in seconds); includeSubDomains extends the rule to every
subdomain; preload opts you into the browser-built-in list.
CAA records — protecting your certificate's auto-renewal
This is the layer most people have never heard of, and it's the one that quietly breaks padlocks.
A CAA record (Certification Authority Authorization) is a small DNS record that lists which Certificate Authorities are allowed to issue an HTTPS certificate for your domain. A Certificate Authority (CA) is one of the trusted companies that hand out HTTPS certs — Let's Encrypt, Google Trust Services, DigiCert, and so on. The CAA record is a whitelist: if a CA isn't on it, browsers won't honour a cert it issues, and a well-behaved CA won't even try.
Why it matters
Cloudflare auto-renews the Universal SSL certificate for your site roughly every 90 days — silently, in the background. Here's the trap: if a CAA record exists but omits a CA that Cloudflare uses, the next auto-renewal fails. You won't notice on day one. Then the old cert expires, and suddenly every visitor sees a broken padlock and a scary "Not secure" warning. A CAA record you added to be safe becomes the thing that takes the site down.
The safe play
Here's the live CAA set for pangaea.id (the letsencrypt.org lines are ours; the rest were
backfilled by Cloudflare):
@ CAA 0 issue "letsencrypt.org"
@ CAA 0 issuewild "letsencrypt.org"
@ CAA 0 issue "pki.goog; cansignhttpexchanges=yes"
@ CAA 0 issuewild "pki.goog; cansignhttpexchanges=yes"
@ CAA 0 issue "ssl.com"
@ CAA 0 issuewild "ssl.com"
@ CAA 0 issue "sectigo.com"
@ CAA 0 issuewild "sectigo.com"
@ CAA 0 issue "comodoca.com"
@ CAA 0 issuewild "comodoca.com"
@ CAA 0 issue "digicert.com; cansignhttpexchanges=yes"
@ CAA 0 issuewild "digicert.com; cansignhttpexchanges=yes"
@ CAA 0 iodef "mailto:[email protected]"
Reading the columns:
@— the apex of the domain (pangaea.iditself).0— a flag;0means "non-critical" (a CA that doesn't understand the record may still proceed).issue— who may issue normal certificates.issuewild— who may issue wildcard certificates (*.pangaea.id). This one is required: Cloudflare's edge certificate is a wildcard, so a CAA set with noissuewildwould block the renewal.iodef— where a CA emails a report if someone requests a cert the policy forbids (mailto:[email protected]).
Verify it
Ask DNS directly what CAA records exist:
dig CAA pangaea.id +short
And confirm who actually issued the live certificate:
openssl s_client -connect www.pangaea.id:443 -servername www.pangaea.id </dev/null 2>/dev/null | openssl x509 -noout -issuer
Status on pangaea.id: configured and verified. The live certificate is issued by Google
Trust Services, and renewal is protected because Cloudflare's CAs are auto-authorised in the CAA
set above.
Security headers — instructions on every response
Headers are short instructions Cloudflare attaches to every response, telling the browser how to behave safely. Four cheap ones close common holes:
X-Content-Type-Options: nosniff— stop the browser from guessing a file's type (which can turn an uploaded image into an executable script). It must trust the declared type only.X-Frame-Options: DENY— forbid anyone from embedding the site inside an<iframe>, which defeats "clickjacking" (a hidden frame tricking users into clicking).Referrer-Policy: strict-origin-when-cross-origin— when a visitor clicks out to another site, don't leak the full URL they came from; share only the bare origin.Permissions-Policy: geolocation=(), microphone=(), camera=()— switch off browser features the site never uses, so no script can prompt for location, mic, or camera.
CSP — the script allowlist
The big one is CSP (Content-Security-Policy): an allowlist of where scripts, styles, fonts and images may load from. Anything not on the list is refused — so an injected or malicious resource simply never runs. This is the strongest single defence against cross-site scripting. Here's the live policy:
default-src 'self'; script-src 'self' {{INLINE_SCRIPT_HASHES}} https://static.cloudflareinsights.com https://www.googletagmanager.com; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data: https://www.googletagmanager.com https://*.google-analytics.com; connect-src 'self' https://cloudflareinsights.com https://api.web3forms.com https://www.googletagmanager.com https://*.google-analytics.com https://*.analytics.google.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'; upgrade-insecure-requests
In plain terms, a few of the key directives:
default-src 'self'— by default, only load things from our own origin. Every other directive narrows from there.font-src 'self'— fonts load only from our own server. The site self-hosts its fonts, so there's no font CDN to allow — the allowlist stays tight.connect-src … https://api.web3forms.com …— the contact form posts to Web3Forms, so that one endpoint is allowed for network requests; everything else is blocked.frame-ancestors 'none'— nobody may frame the site (the modern partner toX-Frame-Options).object-src 'none'— no Flash/plugin objects, ever.upgrade-insecure-requests— quietly rewrite any strayhttp://sub-resource tohttps://.
The interesting part is script-src. There is no 'unsafe-inline' for scripts — which is
what would normally let any inline <script> run. The site's only inline scripts (the
vite-react-ssg bootstrap and the GA4 gtag snippet) are instead pinned by a per-build SHA-256
hash. A build step, apps/labs/scripts/csp-hash.mjs, hashes those exact scripts and writes the
hashes into _headers, replacing the {{INLINE_SCRIPT_HASHES}} placeholder. So only those exact
scripts run — change a single character and the hash no longer matches and the script is refused.
The allowlist can never silently drift.
Anti-bot & firewall, in plain terms
The last three switches keep automated abuse off the site, all free:
- Bot Fight Mode — a one-click switch that challenges obvious bots (scrapers, brute-forcers) before they reach your pages. It already exempts verified search and AI crawlers, so Google, Bing and the answer-engine bots still index the site normally.
- WAF (Web Application Firewall) — Cloudflare's free managed ruleset blocks known attack patterns (SQL injection, common exploit probes) at the edge, before they touch your app. It's a baseline you just turn on.
- Turnstile — Cloudflare's free, privacy-friendly "are you human?" check — the modern, no-puzzle replacement for CAPTCHA. Put it on the contact form only, never the whole site, so real visitors browsing pages never see a challenge — only someone submitting the form is verified.
Do
- Set TLS → Full (strict) and Always Use HTTPS so every leg is encrypted and the insecure door is closed
- Add one CA to CAA and let Cloudflare backfill its own — renewals stay safe
- Scope Turnstile to the contact form, so normal browsing is friction-free
Don't
- Use TLS "Flexible" or "Full" (the second leg ends up unencrypted or unverified)
- Hand-list every CA in CAA and accidentally omit one Cloudflare uses — the next renewal breaks
- Leave Rocket Loader on with a strict CSP, or skip checking the DevTools console after deploy
Cheatsheet
Next
This is Part 4 (security) of the deploy series. The rest of the path: Part 1 · Point the domain at Cloudflare, Part 2 · CI/CD with GitHub Actions → Pages, Part 3 · Root → www redirect, and the caching sibling — HTTP caching on Cloudflare's CDN. The plain-English story of why we layered it this way is in our build diary: Make it fast and safe.
Sources