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.

By Wardzo Team · Updated · 2 min read · Security

Free · no signup · passive checks only · results in under a minute

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.