How to Add Security Headers in Next.js (Complete Guide with Examples)
Learn how to configure all essential security headers in Next.js using next.config.ts. Copy-paste examples for HSTS, CSP, X-Frame-Options, Permissions-Policy, and cross-origin headers — with real-world edge cases like OAuth and OG images.

Next.js makes adding security headers straightforward — but most tutorials only cover the basics. This guide shows you how to configure every essential security header in Next.js, including real-world edge cases that break OAuth flows, OG image crawlers, and embedded content if you get them wrong.
We use Next.js to build SecScanner, so every example here comes from a production app that passes all 54 of our own security checks.
Quick Check: Does Your Next.js App Need Security Headers?
Run a free scan at secscanner.app — paste your URL and you'll see exactly which headers are missing in under 30 seconds. If you see red items for HSTS, X-Frame-Options, CSP, or Referrer-Policy, this guide is for you.
Where to Add Security Headers in Next.js
Next.js provides the headers() function in next.config.ts (or next.config.js). This is the recommended approach because:
- Headers apply to all routes (pages, API routes, static files)
- No middleware overhead — headers are set at the routing layer
- You can use route patterns to apply different headers per path
- Works with both Pages Router and App Router
The Essential Security Headers
Here's a production-ready next.config.ts with all critical security headers:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
// Remove the X-Powered-By: Next.js header
poweredByHeader: false,
async headers() {
return [
{
// Apply to all routes
source: "/:path*",
headers: [
{
key: "Strict-Transport-Security",
value: "max-age=31536000; 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: "geolocation=(), microphone=(), camera=()",
},
{
key: "Cross-Origin-Embedder-Policy",
value: "credentialless",
},
{
key: "Cross-Origin-Opener-Policy",
value: "same-origin",
},
{
key: "Cross-Origin-Resource-Policy",
value: "same-origin",
},
],
},
];
},
};
export default nextConfig;
Let's break down each header and why it matters.
1. Strict-Transport-Security (HSTS)
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Forces browsers to always use HTTPS. Without this header, an attacker on a public Wi-Fi network can intercept the initial HTTP request before the redirect to HTTPS happens (an SSL stripping attack).
max-age=31536000— remember for 1 yearincludeSubDomains— apply to all subdomains toopreload— eligible for browser preload lists (hardcoded HTTPS)
Warning: Only add preload if you're certain all subdomains support HTTPS. Once submitted to the preload list, it's difficult to remove.
2. X-Content-Type-Options
X-Content-Type-Options: nosniff
Prevents browsers from MIME-type sniffing. Without it, a browser might interpret a CSS file as JavaScript or execute an uploaded image as a script — a classic attack vector for file upload vulnerabilities.
3. X-Frame-Options
X-Frame-Options: DENY
Prevents your site from being embedded in iframes. This blocks clickjacking attacks where an attacker overlays your page with invisible elements to trick users into clicking buttons they can't see.
Use SAMEORIGIN instead of DENY if your app uses iframes to embed its own pages.
4. Referrer-Policy
Referrer-Policy: strict-origin-when-cross-origin
Controls how much URL information is shared when users navigate away from your site:
- Same-origin requests: full URL is sent
- Cross-origin requests: only the origin (domain) is sent
- HTTPS→HTTP: nothing is sent
This prevents leaking sensitive URL paths (like /dashboard/user/12345) to external sites.
5. Permissions-Policy
Permissions-Policy: geolocation=(), microphone=(), camera=()
Disables browser features your app doesn't use. If an XSS attack injects a script, it won't be able to access the user's camera or microphone because the policy blocks it at the browser level.
Common features to disable:
Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()
Only list features you actually want to block. If your app uses geolocation, remove it from the list or set geolocation=(self).
6. Cross-Origin Headers (COOP, COEP, CORP)
These three headers work together to isolate your origin and enable powerful security features:
Cross-Origin-Embedder-Policy: credentialless
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
- COEP (
credentialless): Cross-origin resources load without credentials. Usecredentiallessinstead ofrequire-corpto avoid breaking third-party images and scripts that don't set CORP headers. - COOP (
same-origin): Prevents other sites from getting a reference to your window (blocks Spectre-class attacks). - CORP (
same-origin): Prevents other sites from loading your resources.
7. Remove X-Powered-By
// In next.config.ts
poweredByHeader: false,
Next.js adds X-Powered-By: Next.js by default. This reveals your tech stack to attackers. While security through obscurity isn't a defense strategy, there's no reason to advertise your framework.
Real-World Edge Cases
The headers above will break certain features if applied blindly. Here are the edge cases we've hit in production and how to fix them.
OAuth Login Breaks with COOP: same-origin
Cross-Origin-Opener-Policy: same-origin blocks the popup/redirect flow that Google OAuth (and other providers) use. The OAuth callback page can't communicate with the opener window.
Fix: Relax COOP specifically for auth routes:
async headers() {
return [
{
source: "/:path*",
headers: [
// ... all security headers including COOP: same-origin
],
},
// Override COOP for OAuth callback routes
{
source: "/auth/:path*",
headers: [
{
key: "Cross-Origin-Opener-Policy",
value: "same-origin-allow-popups",
},
],
},
{
source: "/api/auth/:path*",
headers: [
{
key: "Cross-Origin-Opener-Policy",
value: "same-origin-allow-popups",
},
],
},
];
}
The more specific routes must come after the catch-all /:path* rule to override it.
OG Images Blocked by CORP: same-origin
If you use Next.js OG image generation (opengraph-image.tsx), social media crawlers (Twitter, Facebook, LinkedIn) can't fetch your OG images because Cross-Origin-Resource-Policy: same-origin blocks cross-origin requests.
Fix: Allow cross-origin access specifically for OG image routes:
{
source: "/:path*/opengraph-image",
headers: [
{
key: "Cross-Origin-Resource-Policy",
value: "cross-origin",
},
],
},
{
source: "/opengraph-image",
headers: [
{
key: "Cross-Origin-Resource-Policy",
value: "cross-origin",
},
],
}
Content-Security-Policy (CSP)
CSP is the most powerful — and most complex — security header. It's not included in the basic config above because a misconfigured CSP will break your app. Here's how to add it properly:
Step 1: Start with Report-Only mode
{
key: "Content-Security-Policy-Report-Only",
value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://your-analytics.com; report-uri /api/csp-report",
}
This logs violations without blocking anything. Monitor the reports for a week.
Step 2: Tighten and enforce
Once you've identified all legitimate sources, switch from Content-Security-Policy-Report-Only to Content-Security-Policy.
Common sources you'll need to whitelist in a Next.js app:
'unsafe-inline'for styles (Tailwind, CSS-in-JS)- Your analytics domain (PostHog, Google Analytics)
- Your CDN for images
- Google Fonts if you use them
For a deeper CSP guide, see our Content Security Policy Masterclass.
Using Middleware Instead (Alternative Approach)
For dynamic CSP with nonces (needed for strict CSP without 'unsafe-inline'), you'll need Next.js middleware:
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import crypto from "crypto";
export function middleware(request: NextRequest) {
const nonce = crypto.randomBytes(16).toString("base64");
const response = NextResponse.next();
response.headers.set(
"Content-Security-Policy",
`default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline';`
);
return response;
}
This approach adds per-request overhead. Only use middleware if you need dynamic nonces — for most apps, the static next.config.ts approach is simpler and faster.
Verifying Your Headers
After deploying, verify your headers are working:
Option 1: curl
curl -I https://your-site.com
Check that all headers appear in the response.
Option 2: Browser DevTools
Open DevTools → Network tab → click on the document request → check Response Headers.
Option 3: SecScanner (Recommended)
Run a free scan at secscanner.app to check all 24 security configurations at once, including headers, TLS, and content security. The scan takes under 30 seconds and tells you exactly what's passing and what needs fixing.
Complete Production Example
Here's the full next.config.ts we use at SecScanner, handling all the edge cases discussed above:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
poweredByHeader: false,
async headers() {
return [
{
source: "/:path*",
headers: [
{
key: "Strict-Transport-Security",
value: "max-age=31536000; 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: "geolocation=(), microphone=(), camera=()",
},
{
key: "Cross-Origin-Embedder-Policy",
value: "credentialless",
},
{
key: "Cross-Origin-Opener-Policy",
value: "same-origin",
},
{
key: "Cross-Origin-Resource-Policy",
value: "same-origin",
},
],
},
// Relax COOP for OAuth flows
{
source: "/auth/:path*",
headers: [
{
key: "Cross-Origin-Opener-Policy",
value: "same-origin-allow-popups",
},
],
},
{
source: "/api/auth/:path*",
headers: [
{
key: "Cross-Origin-Opener-Policy",
value: "same-origin-allow-popups",
},
],
},
// Allow OG images to be fetched by social crawlers
{
source: "/:path*/opengraph-image",
headers: [
{
key: "Cross-Origin-Resource-Policy",
value: "cross-origin",
},
],
},
];
},
};
export default nextConfig;
Common Mistakes
- Adding headers in middleware AND next.config.ts: They don't merge — one overwrites the other. Pick one approach and stick with it.
- Forgetting
poweredByHeader: false: Security headers are less effective if you're advertising your exact framework version. - Setting COOP: same-origin globally: This will break Google OAuth, Sign in with Apple, and any popup-based auth flow. Always create route-specific overrides for auth paths.
- Using COEP: require-corp instead of credentialless:
require-corpblocks all cross-origin resources that don't explicitly set CORP headers — this breaks most third-party images, fonts, and scripts. Usecredentiallessfor a safer default. - Applying CSP without testing: Always start with
Content-Security-Policy-Report-Onlyto avoid breaking your app in production.
Security Headers Checklist for Next.js
- Set
poweredByHeader: falsein next.config.ts - Add HSTS with
includeSubDomainsandpreload - Add
X-Content-Type-Options: nosniff - Add
X-Frame-Options: DENY(or SAMEORIGIN if you iframe yourself) - Add
Referrer-Policy: strict-origin-when-cross-origin - Add
Permissions-Policydisabling unused browser features - Add cross-origin headers (COEP, COOP, CORP) with auth route overrides
- Start CSP in report-only mode, then enforce after testing
- Override CORP to
cross-originfor OG image routes - Run a SecScanner scan to verify all headers are correctly set
Security headers are the lowest-effort, highest-impact security improvement you can make to a Next.js app. The config above takes 5 minutes to add and protects against clickjacking, MIME sniffing, protocol downgrades, and cross-origin attacks. Scan your site now to see your current score.
Related Articles
The Complete Guide to HTTP Security Headers
10 min read
HeadersContent Security Policy Masterclass: From Basics to Advanced Protection
12 min read
TLSHSTS Preload: Force HTTPS for Every Visitor from the First Connection
8 min read
HeadersClickjacking Prevention: X-Frame-Options & CSP Guide
9 min read
Check Your Website Security
Want to see how your website measures up? Run a free security scan with SecScanner to identify vulnerabilities and get actionable remediation guidance.
Scan Your Website Free