Stack guide
Cloudflare security headers checklist (2026 edition)
If your traffic terminates on Cloudflare, the cleanest place to set baseline security headers is at the edge. The origin can still set them, but doing it on Cloudflare guarantees coverage for static assets, redirects, and routes that bypass your app server. This checklist walks through the four headers Scorifya scans for and shows the exact Transform Rule or Worker change to ship.
1. HSTS — force HTTPS at the edge
Use Cloudflare's HSTS dashboard (SSL/TLS → Edge Certificates → HTTP Strict Transport Security) for the canonical case. If you need scoped overrides — say, a specific subdomain that still needs HTTP — set the header in a Transform Rule instead so you keep precision. Either way, start with a 6-month max-age, then widen to 1 year + includeSubDomains once the site has been stable on HTTPS for a quarter.
HSTS missing — nginx (server / location) (nginx)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # If TLS terminates at a CDN, set the header there instead.
HSTS missing — Express (helmet) (typescript)
import helmet from "helmet";
app.use(helmet.hsts({ maxAge: 31_536_000, includeSubDomains: true }));HSTS missing — Apache (vhost) (apache)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Related Scorifya checks: missing_hsts
2. Content Security Policy
Cloudflare's Transform Rule editor lets you `add` a header to all responses without touching origin code. Start with a permissive `Content-Security-Policy-Report-Only` for a week, watch the violations in your reporting endpoint, then flip to `Content-Security-Policy` once the noise dies down. The Workers snippet below is useful when you want CSP nonces tied to an inline script — the Transform Rule path can't generate per-request values.
CSP missing — nginx (nginx)
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'self'" always;
CSP missing — Express (helmet) (typescript)
import helmet from "helmet";
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
baseUri: ["'self'"],
formAction: ["'self'"],
},
}),
);CSP missing — Apache (apache)
Header set Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'self'"
Weak CSP — nginx (tighten script-src) (nginx)
# Drop 'unsafe-inline' / wildcards in production; use nonces or hashes for any inline script. add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'" always;
Weak CSP — Express (helmet) (typescript)
import helmet from "helmet";
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
objectSrc: ["'none'"],
},
}),
);Weak CSP — Apache (apache)
# Replace permissive policy with explicit hosts and no unsafe-inline where possible. Header set Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'"
Related Scorifya checks: missing_csp, weak_csp
3. Framing, MIME sniffing, referrers
These three never need per-request logic, so a single Transform Rule covering all responses is fine. Add `X-Frame-Options: DENY` (or `SAMEORIGIN` if you embed your own pages), `X-Content-Type-Options: nosniff`, and `Referrer-Policy: strict-origin-when-cross-origin` in one rule. Cloudflare evaluates Transform Rules per response, so even cached static assets get them.
X-Frame-Options missing — nginx (nginx)
add_header X-Frame-Options "DENY" always; # Use SAMEORIGIN instead of DENY only if same-origin framing is required.
X-Frame-Options missing — Express (helmet) (typescript)
import helmet from "helmet";
app.use(helmet.frameguard({ action: "deny" }));X-Frame-Options missing — Apache (apache)
Header set X-Frame-Options "DENY"
X-Content-Type-Options missing — nginx (nginx)
add_header X-Content-Type-Options "nosniff" always;
X-Content-Type-Options missing — Express (helmet) (typescript)
import helmet from "helmet"; app.use(helmet.noSniff());
X-Content-Type-Options missing — Apache (apache)
Header set X-Content-Type-Options "nosniff"
Related Scorifya checks: missing_xfo, missing_xcto, missing_referrer_policy
4. Permissions-Policy
The header is verbose, but the principle is straightforward: deny what you don't use. Most marketing sites can get away with a single `Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=()` line. Tighten further as you discover features the page never needs.
Related Scorifya checks: missing_permissions_policy
Background
What is HSTS? HTTP Strict Transport Security explained →
How HSTS works, why the bootstrap window matters, what max-age and includeSubDomains do, and when (or whether) to submit your domain to the browser preload list.
Read more
Verify with a fresh scan
After deploy, run the scanner on the affected hostname. Headers and TLS settings update on the very next request, so you should see the score move within seconds.