HTTP Security Headers Explained — CSP, HSTS, X-Frame-Options & How to Get an A+ Grade
HTTP security headers are response headers your server sends to instruct the browser to enforce security policies — block cross-site scripting, force HTTPS, prevent clickjacking, and restrict which features your page can access. They are the fastest, highest-impact security improvement most sites can make. Six headers, thirty minutes of configuration, dramatically better protection.
6 headers
Cover the most critical threats
A+ grade
Achievable in under 30 minutes
30 min
Typical implementation time
Free
Security header grader tool
What Are HTTP Security Headers?
When a browser receives an HTTP response, it reads certain headers as instructions for how to behave. Security headers are a subset of response headers that tell the browser: what scripts are allowed to run, whether to allow iframes, whether to force HTTPS, what information to share with other sites, and which hardware features the page can access.
Without these headers, the browser uses permissive defaults — which means attackers can inject scripts, embed your page in an iframe, downgrade HTTPS to HTTP, or leak sensitive URLs to third-party trackers.
Content-Security-Policy (CSP)
Controls which scripts, styles, images, and other resources can load. The #1 defense against XSS attacks.
Strict-Transport-Security (HSTS)
Forces the browser to use HTTPS for all future requests to your domain. Prevents protocol downgrade attacks.
X-Frame-Options
Prevents your page from being loaded inside an iframe on another origin. Blocks clickjacking attacks.
X-Content-Type-Options
Tells the browser not to guess the MIME type — it must use the declared Content-Type. Prevents MIME confusion attacks.
Referrer-Policy
Controls how much URL information is included in the Referer header when users navigate away from your site.
Permissions-Policy
Restricts which browser APIs (camera, microphone, geolocation, payment) can be used by your page and embedded iframes.
Grade your headers instantly
Content-Security-Policy (CSP) — #1 Defense Against XSS
A Content Security Policy tells the browser exactly which sources of content are trusted. It can prevent inline scripts from running, block scripts from unknown domains, and stop data from being sent to untrusted endpoints — making XSS attacks much harder to execute even if an attacker manages to inject markup into your page.
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-rAnd0mN0nce' https://cdn.trusted.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https://cdn.trusted.com;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.yoursite.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
upgrade-insecure-requests;Key directives explained:
default-src 'self'— Default: only load resources from your own domainscript-src— Override for scripts: allow your domain + nonce-matched inline scripts + specific CDNsframe-ancestors 'none'— No iframes allowed from any origin (replaces X-Frame-Options)upgrade-insecure-requests— Automatically upgrade HTTP sub-resources to HTTPSbase-uri 'self'— Prevents attackers from changing the base URL via injected <base> tagsform-action 'self'— Forms can only submit to your own domain
Weak CSP vs strict CSP with nonce
unsafe-inline = no XSS protection
# unsafe-inline allows any inline script — CSP basically useless against XSS
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline';Nonce-based CSP = strong XSS protection
# Strict CSP: only scripts with matching nonce attribute are allowed to run
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-rAnd0mN0nce123';
frame-ancestors 'none';
base-uri 'self';Score impact
A missing CSP is worth -20 points in the security grader — the single biggest score deduction of any header. Even a basic CSP with default-src 'self' scores significantly better than no CSP at all.
Strict-Transport-Security (HSTS) — Force HTTPS
HSTS tells the browser: never connect to this domain over HTTP. For the duration specified by max-age, the browser will automatically upgrade all requests to HTTPS without even sending the HTTP request. This prevents protocol downgrade attacks and network interception.
# Basic HSTS — 1 year, applies to subdomains, listed in preload list
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
# Explanation:
# max-age=31536000 → 1 year (in seconds) = the minimum for HSTS preload
# includeSubDomains → applies to all subdomains (api.yoursite.com, etc.)
# preload → request inclusion in browser HSTS preload listsToo-short max-age vs production-ready HSTS
max-age too short
# 7 days is way too short — browser reverts to HTTP after 7 days
Strict-Transport-Security: max-age=604800Production-ready HSTS
# 2 years, all subdomains, preload eligible
Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadHSTS preload requires commitment
X-Frame-Options — Stop Clickjacking
Clickjacking attacks work by embedding your site in a transparent iframe overlaid on a deceptive page. The user thinks they are clicking a harmless button but is actually clicking a button on your site — triggering actions like payments, account changes, or OAuth grants.X-Frame-Options prevents your page from being embedded in iframes.
# DENY — no site can embed this page in an iframe (most restrictive, recommended)
X-Frame-Options: DENY
# SAMEORIGIN — only pages from the same origin can embed this page
X-Frame-Options: SAMEORIGIN
# Modern replacement: CSP frame-ancestors directive (more flexible)
Content-Security-Policy: frame-ancestors 'none'; # equivalent to DENY
Content-Security-Policy: frame-ancestors 'self'; # equivalent to SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self' https://trusted-partner.com; # more specificX-Frame-Options is well-supported everywhere. For modern stacks, prefer the CSP frame-ancestors directive — it is more flexible and supports multiple trusted origins. Set both for maximum compatibility.
# Always use nosniff — tells browser to respect the declared Content-Type
X-Content-Type-Options: nosniff
# Without this, browsers may try to "sniff" the content type — treating
# a text file as JavaScript if it looks like script, which enables attacksHow to Add Security Headers in Express, Nginx, and Next.js
Each framework and server has a different way to add HTTP response headers. Here are copy-paste ready configurations for the three most common setups.
const express = require('express');
const helmet = require('helmet');
const app = express();
// Helmet sets secure defaults for all major security headers
app.use(helmet());
// Or configure each header explicitly:
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'nonce-rAnd0mN0nce'"],
frameAncestors: ["'none'"],
upgradeInsecureRequests: [],
},
},
hsts: {
maxAge: 63072000,
includeSubDomains: true,
preload: true,
},
frameguard: { action: 'deny' },
noSniff: true,
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
})
);server {
listen 443 ssl http2;
server_name yoursite.com;
# Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; frame-ancestors 'none';" always;
# ... rest of config
}/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
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=()',
},
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self'; frame-ancestors 'none';",
},
],
},
];
},
};
module.exports = nextConfig;Generate config automatically
How to Check and Grade Your Security Headers
After implementing security headers, verify they are being sent correctly and get a grade so you know exactly what still needs improvement.
Open DevTools and reload your page
Press F12 → Network tab. Reload the page to capture the initial document request.
Find the main document response
Click the first request in the list (your HTML document). Open the Headers tab and scroll to Response Headers.
Copy all response headers
Right-click in the Response Headers section → "Copy response headers". This copies all headers in plain text format.
Paste into the HTTP Headers Analyzer
Go to unblockdevs.com/http-headers-analyzer and paste the headers into the input box. Click "Analyze Headers".
Review your security grade
The analyzer grades from A+ (all security headers correctly configured) to F (critical headers missing). Each finding includes a description and copy-paste fix.