After this lesson, you will be able to: Set a complete, correct set of HTTP security headers (CSP, HSTS, X-Frame-Options/frame-ancestors, X-Content-Type-Options, Referrer-Policy, Permissions-Policy) in Next.js and test them with securityheaders.com and Mozilla Observatory.
HTTP response headers tell the browser how to treat your site, and the right ones block whole classes of attack: cross-site scripting, clickjacking, MIME sniffing, and protocol downgrade. This lesson covers each major security header, how to write a Content-Security-Policy without breaking your own app, how to set them in next.config.js, and how to grade yourself.
Every HTTP response has a status line, headers, and a body. Headers are metadata: content type, caching, cookies, and security directives. The browser reads security headers before rendering and enforces them. Because they are set once on the response, they are one of the highest-leverage security controls: a few lines of config protect every page.
CSP tells the browser which sources of scripts, styles, images, and connections are allowed. A good policy stops most cross-site scripting: even if an attacker injects a <script>, the browser refuses to run it unless it matches the policy. Inline scripts need a nonce (a per-response random value) or a hash. Start in Content-Security-Policy-Report-Only mode, which logs violations without blocking, so you can find what your own app needs before enforcing. The most common mistake is a policy so strict it breaks your own scripts, so test in report-only first.
Next.js sets response headers via the headers() function. This is a solid baseline; tune the CSP to your app's real sources.
// next.config.jsconst securityHeaders = [{key: "Content-Security-Policy",value: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none'; base-uri 'self';",},{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },{ key: "X-Content-Type-Options", value: "nosniff" },{ key: "X-Frame-Options", value: "DENY" },{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },];module.exports = {async headers() {return [{ source: "/:path*", headers: securityHeaders }];},};
X-Frame-Options: DENY (or CSP frame-ancestors 'none') stops your site being embedded in an attacker's iframe to trick clicks (clickjacking). X-Content-Type-Options: nosniff stops the browser guessing a file's type and executing an uploaded image as a script. Strict-Transport-Security (HSTS) tells browsers to only ever use HTTPS for your domain; includeSubDomains extends it; preload adds you to a list browsers ship with so even the first request is HTTPS. Referrer-Policy controls how much of the URL is sent when users click out (strict-origin-when-cross-origin is a good default). Permissions-Policy disables browser features (camera, mic, geolocation) you do not use, on your pages and in embedded iframes.
Verify after deploying, not just locally.
1. Deploy with the headers set.
2. Run your URL through securityheaders.com and read the grade and missing-header notes.
3. Run it through Mozilla Observatory for a second opinion and CSP analysis.
4. If CSP breaks something, switch to Content-Security-Policy-Report-Only, collect violation reports, widen the policy to your real sources, then re-enforce.
5. Re-scan until you have an A.
Pick one.
Enabling HSTS preload before you are certain every subdomain can do HTTPS forever (preload is hard to undo and can take your site offline on HTTP-only subdomains). Shipping a CSP with 'unsafe-inline' everywhere, which makes it cosmetic. Setting headers only in dev and forgetting they are not applied by your host's CDN. Forgetting frame-ancestors, leaving clickjacking open even with X-Frame-Options missing on some browsers. Testing once and never re-checking after a deploy changes the headers.
Sign in and purchase access to unlock this lesson.