Security Headers Explained: Protect Your Web App Today
Security headers are one of the fastest ways to reduce your web app's attack surface. Learn which headers matter, what they do, and how to configure them.
Your web application might be vulnerable right now, not because of a bug in your code, but because of missing HTTP response headers. Security headers are a layer of protection that browsers enforce on your behalf. They cost almost nothing to add, and they block entire categories of attacks.
This guide covers the headers that matter most, what each one does, and what a correct configuration looks like.
Why Security Headers Matter
When a browser loads your app, it follows instructions embedded in HTTP response headers. Security headers are a set of those instructions that tell the browser how to handle your content safely: which scripts to run, whether to allow your page to be embedded in an iframe, how to handle mixed content, and more.
Without these headers, browsers fall back to permissive defaults that attackers can exploit. With them, you get a meaningful reduction in risk with very little engineering effort.
Penetration testers and automated scanners check for missing security headers on every engagement. If your app is missing them, it will be flagged.
The Headers That Matter
Content-Security-Policy (CSP)
CSP is the most powerful security header and also the most complex to configure. It tells the browser which sources of scripts, styles, images, and other resources are allowed to load on your page.
A site without CSP is vulnerable to cross-site scripting (XSS) attacks that inject malicious scripts into the page. CSP makes those attacks much harder to execute even if an attacker finds an XSS flaw.
A restrictive but practical starting point:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
The key directives:
| Directive | What it controls |
|-----------|-----------------|
| default-src 'self' | Only load resources from your own origin by default |
| script-src 'self' | Only run scripts from your own origin |
| frame-ancestors 'none' | Prevent your page from being embedded in iframes |
| form-action 'self' | Form submissions can only go to your own origin |
If you use third-party scripts (analytics, chat widgets, CDNs), you will need to add those origins explicitly. Use report-uri or report-to to collect violations before enforcing, which helps you build the policy without breaking things.
Strict-Transport-Security (HSTS)
HSTS tells browsers to always use HTTPS when connecting to your domain, even if a user types http:// or follows an HTTP link. It prevents protocol downgrade attacks and cookie hijacking over insecure connections.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age=31536000sets a one-year caching window.includeSubDomainsapplies the rule to all subdomains.preloadopts your domain into browser-maintained preload lists so HTTPS is enforced even on the very first visit.
Only add preload if you are confident your entire domain (and all subdomains) will serve HTTPS. Removing a domain from the preload list takes months.
X-Frame-Options
This header prevents your pages from being embedded in iframes on other sites, blocking clickjacking attacks where an attacker overlays your UI with an invisible frame to trick users into clicking things they did not intend to.
X-Frame-Options: DENY
If you need to allow framing from specific origins, use SAMEORIGIN or configure CSP's frame-ancestors directive instead. CSP frame-ancestors is more flexible and supersedes X-Frame-Options in modern browsers, but keeping both ensures coverage for older browsers.
X-Content-Type-Options
Browsers sometimes try to guess the content type of a response, a behavior called MIME sniffing. An attacker can exploit this by serving a file that looks like an image but contains executable JavaScript.
X-Content-Type-Options: nosniff
This one-liner tells the browser to trust the Content-Type header and never guess. Always include it.
Referrer-Policy
When a user follows a link from your site to an external site, the browser sends a Referer header that can leak sensitive URL paths, query parameters, or internal route structures.
Referrer-Policy: strict-origin-when-cross-origin
This sends the full referrer for same-origin requests (useful for your own analytics) but only the origin (e.g., https://yourapp.com) for cross-origin requests.
Permissions-Policy
Formerly called Feature-Policy, this header controls which browser features (camera, microphone, geolocation, payment APIs) your page can use, and which can be used inside embedded iframes.
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
The empty parentheses disable each feature entirely. Only enable features your app actually uses.
What a Missing Header Costs You
Here is the real-world impact of skipping these headers:
| Missing header | Attack enabled | |----------------|----------------| | No CSP | XSS attacks can exfiltrate session tokens and user data | | No HSTS | Attackers on the same network can intercept and modify traffic | | No X-Frame-Options / CSP frame-ancestors | Clickjacking: users tricked into actions they did not intend | | No X-Content-Type-Options | MIME-sniffing attacks serve malicious scripts as images or fonts | | No Referrer-Policy | Internal URLs and sensitive query params leak to third parties |
These are not theoretical risks. Attackers run automated scanners that identify missing headers and escalate from there.
How to Add Headers in Common Frameworks
Next.js
In next.config.js:
const securityHeaders = [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains; preload',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self'; frame-ancestors 'none'",
},
];
module.exports = {
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }];
},
};
Express / Node.js
Use the helmet package, which sets sensible defaults for all of these headers:
npm install helmet
import helmet from 'helmet';
app.use(helmet());
Helmet's defaults are reasonable, but review and tighten the CSP configuration for your specific app.
Nginx
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'none';" always;
How to Verify Your Headers
After deploying, check your headers with:
curl -I https://yourapp.com
Or use a browser DevTools check: open the Network tab, click your main document request, and look at the Response Headers section.
You can also score your headers against industry standards with tools like securityheaders.com. An A or A+ score means your configuration is solid.
Common Mistakes to Avoid
CSP that is too permissive. unsafe-inline for scripts negates most of CSP's value. If you need inline scripts, use nonces or hashes instead.
HSTS on a site that still serves HTTP. If any part of your app serves HTTP, HSTS will break it for users whose browsers have cached the HSTS rule. Get HTTPS working everywhere first.
Forgetting subdomains. includeSubDomains on HSTS and frame-ancestors on CSP need to account for every subdomain you control.
Setting headers only on HTML pages. Headers should be set on all responses. Attackers can target API endpoints too.
Get a Complete Picture of Your Headers
Security headers are one of the first things vulnerability scanners check. If you want to know what your application is missing across all its routes (not just the homepage), an automated scan gives you a full inventory with remediation guidance.
AI Vulnerability Scanner crawls your entire application and reports missing and misconfigured security headers alongside other vulnerability findings. You get a prioritized list you can act on the same day.