A fresh Next.js app sends almost no security headers. There is no Content Security Policy, no HSTS, no clickjacking protection, and it advertises itself with an X-Powered-By: Next.js header. Adding Next.js security headers takes two pieces: static headers in next.config, and a Content Security Policy with a per-request nonce in proxy.ts (called middleware.ts before Next.js 16). This guide covers both.
Which Next.js Security Headers You Need
These are the Next.js security headers every production app should send, and where each one belongs:
| Header | What it does | Where to set it in Next.js |
|---|---|---|
| Strict-Transport-Security | Forces HTTPS on future visits | next.config (often set by the host too) |
| Content-Security-Policy | Restricts where scripts, styles and other resources load from; the main XSS defense | proxy.ts with a nonce, or next.config for a static policy |
| X-Frame-Options / frame-ancestors | Stops other sites framing yours (clickjacking) | next.config and CSP |
| X-Content-Type-Options | Stops MIME-type sniffing | next.config |
| Referrer-Policy | Limits what URL data leaks to other sites | next.config |
| Permissions-Policy | Turns off browser features you do not use (camera, microphone, geolocation) | next.config |
| Cross-Origin-Opener-Policy | Isolates your window from cross-origin popups | next.config |
For what each header protects against in depth, see website security headers explained. For other servers (Nginx, Apache, Cloudflare), see the general security headers guide.
How to Add Next.js Security Headers
1. Add static headers in next.config
Most Next.js security headers are static and belong in config. The headers() function in next.config applies headers to every matching route. Put everything except the CSP here. Setting poweredByHeader: false also removes the X-Powered-By header, which tells attackers your framework for free.
Only add preload to HSTS once every subdomain serves HTTPS; preload lists are hard to leave.
2. Add a nonce-based Content Security Policy in proxy.ts
A strict CSP blocks inline scripts, but Next.js injects inline scripts for hydration. The recommended fix is a nonce: a random value generated for each request, added to the CSP and to every script Next.js renders. Next.js reads the nonce from the request's CSP header automatically during server rendering.
Two things to know. First, nonces require dynamic rendering: statically generated pages are built before any request exists, so they cannot carry a per-request nonce. Second, third-party scripts you load yourself (analytics, tag managers) need the nonce passed to them; read it with (await headers()).get("x-nonce") and pass it to next/script.
3. Use a static CSP if your pages must stay static
If you rely on static generation and cannot switch to dynamic rendering, set a CSP in next.config instead. You will usually need 'unsafe-inline' for scripts, which weakens XSS protection considerably, so prefer hashes or a nonce where you can. Our Content Security Policy guide explains the trade-offs.
4. Roll out the CSP in report-only mode first
A wrong CSP breaks pages. Ship it as Content-Security-Policy-Report-Only for a week, collect violations, fix the sources you actually need (fonts, analytics, embeds), then switch to the enforcing header.
5. Check your host's headers
Vercel, Netlify and Cloudflare may add or override some headers (HSTS in particular). Look at the real response of your deployed site, not just your config, and make sure headers are not set twice with conflicting values.
6. Verify the deployed result
Next.js security headers only count if they reach the browser. Run your production URL through the security headers checker after every change, and check a few different route types (a static page, a dynamic page, an API route) since the matcher may not cover them all.
Common Next.js Security Headers Mistakes
- A CSP with
'unsafe-inline'and'unsafe-eval'in production, which gives little protection.'unsafe-eval'is only needed in development. - Forgetting
poweredByHeader: false, leavingX-Powered-By: Next.json every response. See server header information disclosure. - A proxy matcher that skips pages, so some routes have no CSP.
- Allowing framing by omitting X-Frame-Options and
frame-ancestors. See the clickjacking guide.
How Scanverra Checks Next.js Security Headers
The Scanverra security headers checker and full security scan fetch your live site and grade HSTS, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy and the Cross-Origin policies, and flag version-revealing headers such as X-Powered-By and Server. When the site runs on Next.js, the fixes point at next.config and proxy.ts rather than generic server config. The check looks at the response of the URL you scan, so test more than one route if your matcher is selective. The Next.js audit page covers performance and SEO checks for Next.js sites too.