Stack guide
Vercel security headers — vercel.json patterns that pass Scorifya
Next.js apps hosted on Vercel can pin security headers in two places: `vercel.json` (works for any Vercel project) or `next.config.js#headers()` (Next-specific, slightly more flexible per-route). For most teams, the `vercel.json` route is plenty, ships fast, and survives framework upgrades. This guide is the minimum diff that turns a fresh Vercel deploy into one that scores 90+ on the headers category.
vercel.json baseline headers
Drop this `vercel.json` snippet into the repo root and redeploy. Every response — `_next/static`, edge functions, API routes, marketing pages — picks up the same baseline. Adjust `script-src`/`connect-src` once you know your third-party tag list. The example covers HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, and Permissions-Policy in one block.
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"
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'"
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_hsts, missing_csp, missing_xfo, missing_xcto, missing_referrer_policy, missing_permissions_policy
When to use next.config.js headers() instead
Use `next.config.js#headers()` when you need different policies per path — for example, a stricter CSP on the marketing pages and a looser one on a dashboard that needs `unsafe-inline` from a vendor SDK. The function returns an array of `{ source, headers }` objects, which Next.js compiles into Vercel's edge config at deploy time.
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: weak_csp
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.