Vercel Security Headers: Complete Configuration Guide for 2026
Vercel security headers are one of the most commonly missed configuration steps for teams deploying to the platform. Vercel's default behavior is to serve whatever headers your application sends — which, for most frameworks, means no security headers at all. Fixing this takes minutes and dramatically improves your security posture.
This guide covers two configuration paths: vercel.json (works with any framework) and Next.js headers() (the recommended approach for Next.js projects). Both approaches are fully copy-paste ready.
Scan your Vercel deployment with ZeriFlow to see exactly which headers are missing before you start.
Method 1: vercel.json Headers Configuration
The vercel.json file at your project root controls deployment configuration including headers, redirects, and rewrites. This approach works for any framework deployed on Vercel — React, Vue, Svelte, Astro, or plain HTML.
Create or update vercel.json in your project root:
{
'headers': [
{
'source': '/(.*)',
'headers': [
{
'key': 'X-Frame-Options',
'value': 'DENY'
},
{
'key': 'X-Content-Type-Options',
'value': 'nosniff'
},
{
'key': 'X-XSS-Protection',
'value': '1; mode=block'
},
{
'key': 'Referrer-Policy',
'value': 'strict-origin-when-cross-origin'
},
{
'key': 'Permissions-Policy',
'value': 'geolocation=(), microphone=(), camera=(), payment=(), usb=()'
},
{
'key': 'Strict-Transport-Security',
'value': 'max-age=31536000; includeSubDomains; preload'
},
{
'key': 'Content-Security-Policy',
'value': 'default-src self; script-src self unsafe-inline; style-src self unsafe-inline; img-src self data: https:; font-src self https://fonts.gstatic.com; connect-src self; frame-ancestors none;'
}
]
},
{
'source': '/api/(.*)',
'headers': [
{
'key': 'Cache-Control',
'value': 'no-store'
}
]
}
]
}Important caveats for vercel.json:
- The source field uses path-to-regexp syntax. /(.*) matches all routes.
- Headers defined in vercel.json are merged with headers your application sends. If your application sends the same header, the application value takes precedence.
- Deploy the updated vercel.json and verify with curl -I https://yourdomain.vercel.app.
Method 2: Next.js headers() Configuration
For Next.js projects, the headers() function in next.config.js (or next.config.mjs) is the idiomatic way to configure HTTP headers. It runs at build time and has access to Next.js's full path matching capabilities.
// next.config.js
const securityHeaders = [
{
key: 'X-DNS-Prefetch-Control',
value: 'on'
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'X-XSS-Protection',
value: '1; mode=block'
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin'
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()'
},
{
key: 'Content-Security-Policy',
value: ContentSecurityPolicy.replace(/
/g, '')
}
]
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: securityHeaders,
},
]
},
}Building a robust CSP for Next.js:
Next.js uses inline scripts for hydration, which complicates strict CSP implementation. The cleanest approach uses nonces:
// middleware.js (Next.js 13+ App Router)
import { NextResponse } from 'next/server'
import crypto from 'crypto'
export function middleware(request) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
style-src 'self' 'nonce-${nonce}';
img-src 'self' blob: data: https:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
block-all-mixed-content;
upgrade-insecure-requests;
`
const contentSecurityPolicyHeaderValue = cspHeader.replace(/\s{2,}/g, ' ').trim()
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-nonce', nonce)
requestHeaders.set('Content-Security-Policy', contentSecurityPolicyHeaderValue)
const response = NextResponse.next({ request: { headers: requestHeaders } })
response.headers.set('Content-Security-Policy', contentSecurityPolicyHeaderValue)
return response
}
export const config = {
matcher: [
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
],
}Then in your layout.tsx:
import { headers } from 'next/headers'
export default function RootLayout({ children }) {
const nonce = headers().get('x-nonce')
return (
<html>
<head>
<script nonce={nonce} src='/your-inline-script.js' />
</head>
<body>{children}</body>
</html>
)
}HSTS: Long-Term HTTPS Enforcement
The Strict-Transport-Security header in both configurations above uses max-age=63072000 (2 years), which is the value recommended for HSTS preload list inclusion. The preload directive allows your domain to be submitted to browser vendors' hard-coded HTTPS-only lists.
Before adding `preload`:
- Ensure ALL subdomains are HTTPS-ready (including www, api, cdn, etc.)
- Ensure you are committed to HTTPS permanently — preload removal takes months
- Test with max-age=300 (5 minutes) first, then increase gradually
HTTPS Redirects on Vercel
Vercel automatically serves all deployments over HTTPS and redirects HTTP to HTTPS by default. You do not need to configure this manually. However, you can verify it is working and add additional redirect rules in vercel.json:
{
'redirects': [
{
'source': '/(.*)',
'has': [{ 'type': 'header', 'key': 'x-forwarded-proto', 'value': 'http' }],
'destination': 'https://yourdomain.com/$1',
'permanent': true
}
]
}This redirect rule catches any HTTP requests that somehow reach Vercel's origin (in practice, Vercel handles this at the edge, but this adds a belt-and-suspenders guarantee).
Verifying With ZeriFlow
After deploying your header changes, run a ZeriFlow scan on your Vercel domain to confirm: - All security headers are present in HTTP responses - CSP is syntactically correct and present - HSTS max-age is sufficient - No mixed content is detected - X-Frame-Options or frame-ancestors CSP directive is present
FAQ
### Q: Do Vercel headers apply to preview deployments?
A: Yes. Headers configured in vercel.json or next.config.js apply to all deployments — production, preview, and development (via vercel dev). This is useful for catching security regressions in PRs before they reach production.
### Q: Why does my CSP break Next.js hot reloading in development?
A: Next.js development mode uses WebSocket connections and inline scripts that violate strict CSP. Use process.env.NODE_ENV === 'development' to apply a relaxed CSP in development and the strict policy in production only.
### Q: Can I use environment variables in vercel.json headers?
A: Not directly — vercel.json is a static JSON file. For dynamic headers, use Next.js middleware or API routes where you have access to environment variables at request time.
### Q: How do I handle third-party scripts (analytics, chat widgets) in my CSP?
A: Add the script source to your script-src directive and any API endpoints they call to connect-src. For Google Analytics 4: add https://www.googletagmanager.com and https://www.google-analytics.com to both script-src and connect-src. Review the documentation for each third-party service — most provide CSP requirements in their integration docs.
### Q: What is the difference between X-Frame-Options and frame-ancestors in CSP?
A: X-Frame-Options is the older mechanism, supported by all browsers. frame-ancestors in CSP is more powerful — it can specify multiple allowed origins and is not ignored in cross-origin iframes like XFO sometimes is. Best practice is to set both: X-Frame-Options: DENY for legacy browser support and frame-ancestors 'none' in your CSP for complete coverage.
Conclusion
Vercel security headers require minimal effort to configure but provide substantial protection. The vercel.json approach takes five minutes and works immediately. The Next.js headers() approach with nonce-based CSP takes longer but provides the strongest security posture possible for a Next.js application.
Neither approach requires any Vercel paid plan features — security headers work identically on free, Pro, and Enterprise tiers.
Verify your complete security configuration with ZeriFlow after deploying. The 80+ automated checks will confirm your headers are correct, your HTTPS is configured properly, and flag any remaining issues with clear remediation guidance.