Security headers explained: a practical setup for Next.js, Nginx and Caddy
What each HTTP security header does, sensible values to start with, and copy-paste configuration for Next.js, Nginx and Caddy, including a nonce-based Content-Security-Policy.
Security headers are a few lines of configuration that tell browsers how to protect your visitors. Here is what each one does and a sensible starting value.
The headers that matter
| Header | What it does | Starting value |
|---|---|---|
Strict-Transport-Security |
Forces HTTPS for future visits | max-age=31536000; includeSubDomains |
Content-Security-Policy |
Limits where scripts, styles and other resources can load from | See below |
X-Content-Type-Options |
Stops browsers guessing file types | nosniff |
Referrer-Policy |
Limits what URL data is sent to other sites | strict-origin-when-cross-origin |
X-Frame-Options / frame-ancestors |
Prevents clickjacking via iframes | DENY / frame-ancestors 'none' |
Permissions-Policy |
Turns off browser features you do not use | camera=(), microphone=(), geolocation=() |
Only add preload to HSTS once you are sure every subdomain supports HTTPS. Preload lists are hard to leave.
Content-Security-Policy without 'unsafe-inline'
A CSP that allows 'unsafe-inline' scripts gives little protection against cross-site scripting. The modern approach is a nonce: a random value generated for each response, added to the CSP header and to every legitimate <script> tag.
Content-Security-Policy: default-src 'self'; script-src 'nonce-RANDOM' 'strict-dynamic'; style-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'
'strict-dynamic' lets scripts loaded by your trusted scripts run, so framework code-splitting keeps working.
Next.js
Static headers go in next.config.js:
async headers() {
return [{ source: '/:path*', headers: [
{ key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'X-Frame-Options', value: 'DENY' },
] }];
}
For a nonce-based CSP, generate the nonce in middleware.js, set it on both the request and the response Content-Security-Policy header, and Next.js applies it to its own scripts. Pages must be rendered dynamically for the nonce to change per request.
Nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-Frame-Options "DENY" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
Caddy
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
X-Frame-Options "DENY"
-Server
}
Test after every change
Headers break silently when a proxy, CDN or framework upgrade changes the configuration. Check your security headers after each deploy, or let Wardzo re-check them every week.
Related
- Security headers checker
Free security headers checker. Test HSTS, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, SSL certificate expiry, TLS version and mixed content.
- What is a Content Security Policy (CSP)?
A Content Security Policy is an HTTP header that tells browsers which sources of scripts, styles and other resources a page may load, limiting the damage of cross-site scripting.
- What is HSTS (HTTP Strict Transport Security)?
HSTS is a security header that tells browsers to always use HTTPS for a site, preventing downgrade attacks and insecure first requests.