Security Headers for Vibe Coders: Ship Secure Without the Corporate BS
You're shipping fast. Your Next.js app is deployed on Vercel. Your API routes are working. Your auth flow is solid. But when you check your security headers, you're missing half of them—or worse, you have none at all. Sound familiar?
Here's the thing: security headers are one of those "set it and forget it" security wins that take 10 minutes to implement but protect you from a whole category of attacks. They're HTTP response headers that tell browsers how to handle your site. No complex infrastructure, no third-party services, just configuration.
This guide is for builders who want to secure their apps without drowning in security documentation. We'll cover the essential security headers, give you copy-paste configs for your stack, and show you how to test them. No corporate fluff, just practical stuff that works.
TL;DR
Most of these take one line of config. The hard part is knowing which ones you need and how to set them for your stack.
Why This Matters (Beyond "Security is Important")
Let's be real: you're not implementing security headers because you're paranoid. You're doing it because:
It's free security. These headers don't cost anything, don't require new infrastructure, and don't slow down your site. They're configuration, not code.
SEO actually cares. Search engines favor sites with proper HTTPS enforcement (HSTS) and secure configurations. It's a ranking signal, even if it's minor.
Users notice. Modern browsers show security indicators. Missing headers can trigger warnings or make your site look less trustworthy.
It takes 10 minutes. Seriously. Once you know what to set, you can configure all essential headers in under 10 minutes. The ROI is insane.
It prevents real attacks. XSS, clickjacking, MIME-sniffing—these aren't theoretical. They're common attack vectors that security headers block automatically.
Common Mistakes (Don't Do These)
1. Missing Headers Entirely
The most common mistake is not setting headers at all. Your framework might not add them by default, and many hosting providers don't either. Check what you're currently sending—chances are, you're missing most of them.
2. Overly Restrictive CSP Breaking Functionality
Content-Security-Policy is powerful, but it's easy to break your site with it. If you copy-paste a restrictive CSP without testing, you'll block legitimate scripts, styles, or API calls. Start permissive, tighten gradually.
3. Setting Headers on HTTP Instead of HTTPS
Some headers (like HSTS) only work over HTTPS. Setting them on HTTP responses does nothing, and browsers ignore them. Make sure you're setting headers on your HTTPS endpoints.
4. Forgetting to Test After Implementation
You added headers, deployed, and called it done. But did you verify they're actually being sent? Check your response headers in browser dev tools or use a security scanner. Headers can be overridden or not set correctly.
5. Copy-Pasting Configs Without Understanding
It's tempting to grab a config from Stack Overflow and call it done. But if you don't understand what each header does, you might be blocking legitimate functionality or missing important protections.
6. Not Updating Headers as Your App Evolves
Your CSP worked when you had three scripts. Now you're using analytics, a CDN, and third-party widgets. Your old CSP is probably blocking things. Headers need maintenance, just like your code.
7. Ignoring Header Conflicts
Some headers overlap (like X-Frame-Options and CSP's frame-ancestors). If you set both, browsers use the more restrictive one. Know which headers work together and which conflict.
The "Do This Now" Checklist
Here's your action plan. Do this in order:
Content-Security-Policy (CSP): The XSS Killer
What it does: CSP prevents cross-site scripting (XSS) attacks by controlling which resources the browser can load and execute. It's a whitelist: if it's not allowed, it's blocked.
The quick fix: Start with a permissive policy, then tighten it as you identify what your app actually needs.
Next.js / Vercel example:
If you're on Vercel, add this to your next.config.ts:
// next.config.ts
const nextConfig = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; frame-ancestors 'none';",
},
],
},
];
},
};Or use Next.js middleware:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;"
);
return response;
}Cloudflare example:
If you're using Cloudflare, add headers in your _headers file or via Workers:
# _headers
/*
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';nginx example:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;Pro tip: Start with unsafe-inline and unsafe-eval if you need them (many frameworks do), then gradually remove them as you refactor. Perfect is the enemy of shipped.
Want to dive deeper? Check out our guide on Content-Security-Policy.
Strict-Transport-Security (HSTS): Force HTTPS Forever
What it does: HSTS tells browsers "always use HTTPS for this domain, even if the user types HTTP." It prevents protocol downgrade attacks and ensures encrypted connections.
The quick fix: Set it once, browsers remember for the duration you specify (usually one year).
Next.js / Vercel example:
// next.config.ts
const nextConfig = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains; preload',
},
],
},
];
},
};Cloudflare example:
Cloudflare handles HSTS automatically if you enable it in the dashboard. Or set it manually:
# _headers
/*
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadnginx example:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;Important: Only set HSTS over HTTPS. Setting it over HTTP does nothing, and browsers ignore it. Also, preload is optional—it adds your domain to browser preload lists, but requires additional setup.
Learn more about HSTS configuration.
X-Frame-Options: Don't Get Clickjacked
What it does: Prevents your site from being embedded in frames (iframes), which stops clickjacking attacks where malicious sites overlay your content.
The quick fix: Use DENY unless you need same-origin framing, then use SAMEORIGIN.
Next.js / Vercel example:
// next.config.ts
{
key: 'X-Frame-Options',
value: 'DENY',
}Cloudflare / nginx example:
X-Frame-Options: DENYNote: Modern CSP's frame-ancestors directive does the same thing. If you're using CSP, you can skip X-Frame-Options, but setting both doesn't hurt (browsers use the more restrictive).
Check out our X-Frame-Options guide for more details.
Referrer-Policy & Permissions-Policy: Privacy and Feature Control
Referrer-Policy controls how much referrer information leaks to third parties. Permissions-Policy disables browser features you don't need.
Referrer-Policy quick fix:
Use strict-origin-when-cross-origin for a good balance of privacy and functionality:
// next.config.ts
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
}Permissions-Policy quick fix:
Disable features you don't need. Most apps don't need camera, microphone, or geolocation:
// next.config.ts
{
key: 'Permissions-Policy',
value: 'geolocation=(), microphone=(), camera=(), payment=(self)',
}What this does: Blocks geolocation, microphone, and camera access entirely. Allows payment API only for same-origin requests. Adjust based on what your app actually needs.
X-Content-Type-Options: Stop MIME-Sniffing
What it does: Prevents browsers from guessing content types, which can lead to XSS vulnerabilities.
The quick fix: Just set it to nosniff. That's it.
// next.config.ts
{
key: 'X-Content-Type-Options',
value: 'nosniff',
}Security Headers Quick Reference Table
| Header | What It Does | Recommended Value | Notes |
|--------|-------------|-------------------|-------|
| Content-Security-Policy | Prevents XSS by controlling resource loading | default-src 'self'; script-src 'self' 'unsafe-inline' | Start permissive, tighten gradually |
| Strict-Transport-Security | Forces HTTPS connections | max-age=31536000; includeSubDomains; preload | Only set over HTTPS |
| X-Frame-Options | Prevents clickjacking | DENY or SAMEORIGIN | Use DENY unless you need framing |
| Referrer-Policy | Controls referrer information leakage | strict-origin-when-cross-origin | Good balance of privacy/functionality |
| Permissions-Policy | Disables unnecessary browser features | geolocation=(), microphone=(), camera=() | Customize based on your needs |
| X-Content-Type-Options | Prevents MIME-sniffing | nosniff | Set it and forget it |
Complete Next.js Configuration Example
Here's a complete next.config.ts with all essential headers:
// next.config.ts
const nextConfig = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
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' data:; connect-src 'self' https:; frame-ancestors 'none';",
},
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains; preload',
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'geolocation=(), microphone=(), camera=(), payment=(self)',
},
],
},
];
},
};
module.exports = nextConfig;Frequently Asked Questions
Do I need all security headers?
Not necessarily. The essentials are CSP, HSTS, X-Frame-Options, and X-Content-Type-Options. Referrer-Policy and Permissions-Policy are nice-to-haves that improve privacy and reduce attack surface. Start with the essentials, add others as needed.
Will security headers break my site?
They can, especially CSP. If you copy-paste a restrictive CSP without testing, you might block legitimate scripts or styles. Always test headers in development first. Start permissive, tighten gradually. Most other headers (HSTS, X-Frame-Options, etc.) are safe to add without breaking anything.
How do I test security headers?
Easiest way: Open browser dev tools → Network tab → Click any request → Check Response Headers. You should see your security headers listed there. Or use a security scanner—run a free scan and get a detailed report of what headers you're missing.
What's the difference between X-Frame-Options and CSP frame-ancestors?
They do the same thing (prevent framing), but CSP's frame-ancestors is the modern way. If you're using CSP, you can skip X-Frame-Options. Setting both doesn't hurt—browsers use the more restrictive one. X-Frame-Options is simpler if you're not using CSP.
Do security headers affect performance?
Negligibly. Headers add a few bytes to each HTTP response, but the performance impact is essentially zero. The security benefits far outweigh any minimal overhead. Don't skip headers for performance reasons.
Can I set different headers for different routes?
Yes. In Next.js, you can specify different source patterns and set different headers for each. For example, you might want stricter CSP on your admin routes or different headers for API endpoints. Configure headers per route as needed.
Ship This: Your Next Steps
You've read the guide. Now implement it. Here's your action plan:
1. Check what you have: Run a free security scan to see which headers you're missing
2. Add headers for your stack: Copy the configs above for Next.js, Vercel, Cloudflare, or nginx
3. Test in development: Don't deploy without testing—especially CSP
4. Deploy and verify: Check that headers are actually being sent
5. Iterate: Tighten CSP as you identify what your app needs
Security headers are configuration, not code. They're one of the easiest security wins you can implement. Set them once, and they protect your site automatically.
Ready to check your current security headers? Run a free scan and get a detailed report in seconds. No signup required, just paste your URL and see what's missing.
Want to dive deeper into specific headers? Check out our guides on Content-Security-Policy, HSTS, and X-Frame-Options. Or browse our security blog for more practical guides.
Ship secure. Don't get owned. Set your headers.