Security Headers in 5 Minutes: Copy-Paste Config for nginx, Apache, Cloudflare, Vercel
Exact copy-paste security header configurations for all six essential headers across nginx, Apache, Cloudflare Workers, and Vercel's next.config.ts — with explanations of what each header does and why it matters.
HTTP security headers are the fastest, highest-leverage security improvement you can make to a web application. They require no code changes to your application logic, take minutes to configure, and protect against entire classes of attacks. This guide provides ready-to-use configurations for every major deployment platform.
The Six Essential Headers
Before the configs, here is what each header does and why it belongs in every web application:
1. Strict-Transport-Security (HSTS)
Tells browsers to only connect to your site over HTTPS, even if the user types http:// or follows an HTTP link. Prevents SSL stripping attacks where an attacker downgrades your connection to HTTP.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
max-age=63072000: Cache for 2 yearsincludeSubDomains: Apply to all subdomainspreload: Eligible for browser preload lists (only add if all subdomains support HTTPS)
2. X-Frame-Options Prevents your site from being embedded in an iframe. Mitigates clickjacking attacks where an attacker overlays your UI inside their malicious page.
X-Frame-Options: DENY
Use DENY (no framing ever) or SAMEORIGIN (allow framing from same origin). The CSP frame-ancestors directive provides finer-grained control and supersedes X-Frame-Options in modern browsers — but X-Frame-Options provides fallback for older browsers.
3. X-Content-Type-Options Prevents browsers from "MIME sniffing" — inferring a different content type than what the server declares. Without this, an attacker who can upload a file might trick the browser into executing it as JavaScript.
X-Content-Type-Options: nosniff
This is a single-value header with no configuration options. Always set it.
4. Referrer-Policy Controls how much referrer information the browser includes in requests. Protects user privacy and prevents leaking sensitive URL parameters to third parties.
Referrer-Policy: strict-origin-when-cross-origin
strict-origin-when-cross-origin: Sends full URL for same-origin requests, only origin for cross-origin HTTPS→HTTPS, and nothing for HTTPS→HTTP. A good default.no-referrer: Maximum privacy — no referrer ever. May break analytics.same-origin: Only sends referrer for same-origin requests.
5. Permissions-Policy (formerly Feature-Policy) Restricts which browser APIs your site can use, preventing injected scripts from accessing sensitive capabilities.
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()
Empty parentheses () disable the feature entirely. Your specific list depends on which APIs your application legitimately uses.
6. Content-Security-Policy Defines approved sources for all resource types. The most powerful header, but also the most complex. See the dedicated CSP guide for implementation details. A basic starting policy:
Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests
nginx Configuration
Add this to your server {} block (or a shared include file):
# /etc/nginx/snippets/security-headers.conf
# HSTS: 2 years, include subdomains, preload eligible
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Clickjacking protection
add_header X-Frame-Options "DENY" always;
# MIME type sniffing prevention
add_header X-Content-Type-Options "nosniff" always;
# Referrer privacy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Browser feature restrictions
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
# Basic CSP (customize for your application)
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests" always;
Include this file in your site configuration:
server {
listen 443 ssl http2;
server_name example.com;
include snippets/security-headers.conf;
# ... rest of your server block
}
The always flag ensures headers are added for all response codes, including error responses (4xx, 5xx). Without always, nginx only adds headers to 2xx responses.
Verify with curl:
curl -I https://example.com | grep -E "Strict-Transport|X-Frame|X-Content|Referrer|Permissions|Content-Security"
Apache Configuration
Apache uses Header always set for the same effect as nginx's always flag.
For Apache 2.4+, in your <VirtualHost> block or .htaccess:
# Requires mod_headers (enable with: sudo a2enmod headers)
# HSTS
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
# Clickjacking
Header always set X-Frame-Options "DENY"
# MIME sniffing
Header always set X-Content-Type-Options "nosniff"
# Referrer
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Feature restrictions
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()"
# CSP
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests"
For .htaccess (shared hosting):
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>
Enable mod_headers if not active:
sudo a2enmod headers
sudo systemctl restart apache2
Cloudflare Configuration
Cloudflare offers two approaches: Transform Rules (for managed headers without code) and Workers (for programmatic header injection).
Option A: Transform Rules (No Code Required)
In the Cloudflare dashboard: Rules > Transform Rules > Modify Response Header > Create Rule.
Create one rule covering all requests (* URI wildcard), with "Set" actions for each header:
| Header Name | Value |
|---|---|
Strict-Transport-Security | max-age=63072000; includeSubDomains; preload |
X-Frame-Options | DENY |
X-Content-Type-Options | nosniff |
Referrer-Policy | strict-origin-when-cross-origin |
Permissions-Policy | camera=(), microphone=(), geolocation=() |
Option B: Cloudflare Worker
For CSP with dynamic nonces or headers that vary by request, use a Worker:
export default {
async fetch(request, env) {
const response = await fetch(request);
const newResponse = new Response(response.body, response);
newResponse.headers.set('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload');
newResponse.headers.set('X-Frame-Options', 'DENY');
newResponse.headers.set('X-Content-Type-Options', 'nosniff');
newResponse.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
newResponse.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
newResponse.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
);
return newResponse;
}
};
Important Cloudflare note: Cloudflare itself sets the HSTS Strict-Transport-Security header when SSL is enabled in the dashboard (under SSL/TLS > Edge Certificates > Enable HSTS). If you add it via Transform Rules or Workers, you may end up with duplicate headers. Check your existing headers before adding them.
Vercel Configuration
For Next.js on Vercel, configure headers in next.config.ts:
// next.config.ts
import type { NextConfig } from 'next';
const securityHeaders = [
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; 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: 'camera=(), microphone=(), geolocation=(), payment=(), usb=()'
},
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com",
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"img-src 'self' data: blob: https:",
"font-src 'self' https://fonts.gstatic.com",
"connect-src 'self' https://vitals.vercel-insights.com https://www.google-analytics.com",
"object-src 'none'",
"base-uri 'self'",
"frame-ancestors 'none'",
"upgrade-insecure-requests"
].join('; ')
}
];
const nextConfig: NextConfig = {
async headers() {
return [
{
// Apply to all routes
source: '/(.*)',
headers: securityHeaders
}
];
}
};
export default nextConfig;
For a nonce-based CSP in Next.js (eliminating 'unsafe-inline'), use middleware as described in the CSP guide. The next.config.ts headers approach does not have access to request-time nonces.
Vercel-specific note: Vercel automatically adds HSTS in production. If you add it in next.config.ts as well, you will see a duplicate header. This is harmless but can be cleaned up by removing the HSTS entry from securityHeaders if you see it duplicated in production responses.
Testing Your Headers
Once deployed, test your headers:
# Check all headers at once
curl -sI https://example.com | grep -iE "strict-transport|x-frame|x-content|referrer|permissions|content-security"
Online tools:
- securityheaders.com: Grades your headers and provides specific recommendations
- Mozilla Observatory (observatory.mozilla.org): Comprehensive header security analysis
- HSTS Preload Check (hstspreload.org): Verifies your site is eligible for preloading
A site with all six headers correctly configured will score A+ on securityheaders.com and A+ on Mozilla Observatory. This is achievable in under 30 minutes for most deployment configurations.