Why HTTP Security Headers Matter
HTTP security headers are directives your server sends alongside every response. They tell the browser how to behave: which scripts to trust, whether framing is allowed, how to handle HTTPS, and which browser APIs your page actually needs. Without them, you leave every decision to browser defaults — and those defaults are permissive by design.
According to a 2025 analysis by Scott Helme of the top one million sites, fewer than 12% deploy a Content Security Policy. Only 25% use Permissions-Policy. That means the vast majority of production websites are missing defenses that take minutes to configure and cost nothing to run.
The impact is measurable. Security headers defend against entire classes of attacks:
- Content-Security-Policy blocks cross-site scripting (XSS), the most common web vulnerability for over a decade.
- Strict-Transport-Security prevents SSL stripping and downgrade attacks.
- X-Frame-Options stops clickjacking, where attackers overlay invisible iframes to hijack user clicks.
- X-Content-Type-Options prevents browsers from guessing MIME types, which can turn uploaded text files into executable scripts.
- Referrer-Policy controls how much URL information leaks to third parties.
- Permissions-Policy disables browser features your site never uses, reducing your attack surface to zero for those APIs.
This guide covers each header in depth with working configuration examples for the three most common server environments: Apache, Nginx, and Express.js. You can also generate CSP headers automatically with NexTool's CSP Header Generator.
Quick Reference Table
If you want the short version, here is every header you should set, what it does, and its recommended value:
| Header | Purpose | Recommended Value |
|---|---|---|
| Content-Security-Policy | Controls which resources the browser can load | default-src 'self'; script-src 'self' (customize per site) |
| Strict-Transport-Security | Forces HTTPS for all future visits | max-age=31536000; includeSubDomains; preload |
| X-Frame-Options | Blocks clickjacking via iframes | DENY or SAMEORIGIN |
| X-Content-Type-Options | Prevents MIME type sniffing | nosniff |
| Referrer-Policy | Controls referrer URL sharing | strict-origin-when-cross-origin |
| Permissions-Policy | Restricts browser feature access | camera=(), microphone=(), geolocation=() |
Now let us go through each one in detail.
Content-Security-Policy (CSP)
Content Security Policy is the most powerful and the most complex security header. It defines an allowlist of content sources for every resource type your page loads: scripts, styles, images, fonts, connections, frames, and more. Any resource not explicitly permitted is blocked by the browser.
Why CSP Matters
XSS attacks work by injecting malicious scripts into your page. A strict CSP makes those injected scripts useless because the browser refuses to execute anything not on the allowlist. Even if an attacker finds an injection point in your HTML, CSP is the safety net that prevents execution.
Key Directives
default-src— Fallback for all resource types not explicitly covered. Start with'none'or'self'.script-src— Controls JavaScript sources. Avoid'unsafe-inline'and'unsafe-eval'whenever possible.style-src— Controls stylesheet sources.'unsafe-inline'is often needed for CSS-in-JS frameworks.img-src— Controls image sources. Typically'self' data: https:.connect-src— Controls fetch, XHR, and WebSocket destinations.frame-ancestors— Controls who can embed your page in an iframe. Modern replacement for X-Frame-Options.base-uri— Restricts the<base>element. Set to'self'to prevent base tag hijacking.form-action— Restricts where forms can submit. Prevents form action hijacking.
Configuration Examples
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests;" always;
Header always set Content-Security-Policy "\
default-src 'self'; \
script-src 'self' https://cdn.example.com; \
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; \
img-src 'self' data: https:; \
font-src 'self' https://fonts.gstatic.com; \
connect-src 'self' https://api.example.com; \
frame-ancestors 'none'; \
base-uri 'self'; \
form-action 'self'; \
upgrade-insecure-requests;"
import helmet from 'helmet';
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://cdn.example.com"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
imgSrc: ["'self'", "data:", "https:"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
connectSrc: ["'self'", "https://api.example.com"],
frameAncestors: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
upgradeInsecureRequests: [],
},
}));
Deploy your CSP using the Content-Security-Policy-Report-Only header first. It logs violations to the browser console (and optionally to a report-uri endpoint) without blocking anything. Once you have resolved all legitimate violations, switch to enforcement by using the Content-Security-Policy header.
Building a CSP by hand is tedious and error-prone. NexTool's CSP Header Generator walks you through each directive with a visual interface and produces the final header string in all three server formats.
Strict-Transport-Security (HSTS)
The HSTS header tells browsers: "This site must always be loaded over HTTPS. If someone types http://, convert it to https:// before even sending the request." This prevents SSL stripping attacks, where a man-in-the-middle downgrades the connection to unencrypted HTTP.
Directives
max-age— How long (in seconds) the browser should remember to only use HTTPS. Use31536000(one year) for production.includeSubDomains— Applies HSTS to all subdomains. Important if you use subdomains for APIs or apps.preload— Signals that you want your domain added to browser HSTS preload lists. Once preloaded, browsers enforce HTTPS even on the first visit, before they have ever seen your HSTS header.
HSTS is difficult to undo. Once a browser caches the header, it will refuse plain HTTP connections for the full max-age duration. Before enabling, make sure every page, asset, and subdomain on your site works correctly over HTTPS. Start with a short max-age (e.g., 300 seconds) during testing, then increase to one year once verified.
Configuration Examples
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
import helmet from 'helmet';
app.use(helmet.hsts({
maxAge: 31536000, // 1 year in seconds
includeSubDomains: true,
preload: true,
}));
If you are using Apache and want to generate a complete .htaccess file with HSTS and other security headers, NexTool's .htaccess Generator handles the syntax for you.
X-Frame-Options
X-Frame-Options prevents your page from being loaded inside an <iframe>, <frame>, or <object> on another site. This blocks clickjacking attacks, where an attacker places your page in an invisible iframe and tricks users into clicking buttons they cannot see.
Values
DENY— The page cannot be framed by any site, including your own. Use this unless you explicitly embed your own pages.SAMEORIGIN— The page can only be framed by pages on the same origin. Use this if you have legitimate iframes (e.g., embedding your own checkout page).
The older ALLOW-FROM uri value was never widely supported and is now deprecated. Use CSP frame-ancestors instead if you need to whitelist specific external domains.
Configuration Examples
add_header X-Frame-Options "DENY" always;
Header always set X-Frame-Options "DENY"
import helmet from 'helmet';
// Helmet sets X-Frame-Options to SAMEORIGIN by default
app.use(helmet.frameguard({ action: 'deny' }));
CSP frame-ancestors is the modern, more flexible replacement. It supports whitelisting specific domains (e.g., frame-ancestors 'self' https://trusted.com). However, you should still set X-Frame-Options as a fallback for older browsers that do not support CSP Level 2. Set both for maximum coverage.
X-Content-Type-Options
This header has exactly one valid value: nosniff. It tells the browser to trust the Content-Type header your server sends and never try to "sniff" the content to guess a different type.
Why This Matters
Without nosniff, a browser might interpret a file you serve as text/plain as JavaScript or HTML if the content looks like code. An attacker who uploads a file with JavaScript disguised as an image or text file could exploit this behavior to execute scripts in the context of your domain.
Configuration Examples
add_header X-Content-Type-Options "nosniff" always;
Header always set X-Content-Type-Options "nosniff"
import helmet from 'helmet';
// Helmet enables noSniff by default
app.use(helmet.noSniff());
This is the simplest header to configure. There is no reason not to set it on every production site.
Referrer-Policy
When a user clicks a link on your site and navigates to another domain, the browser sends a Referer header (yes, misspelled in the HTTP spec since 1996) containing the URL they came from. Depending on your URL structure, this can leak sensitive information like user IDs, search queries, tokens, or internal paths.
Common Values
| Value | Behavior | When to Use |
|---|---|---|
no-referrer |
Never send referrer information | Maximum privacy, but breaks analytics |
strict-origin-when-cross-origin |
Full URL for same-origin, origin-only for cross-origin HTTPS, nothing for HTTPS to HTTP | Best balance of privacy and functionality (recommended) |
same-origin |
Full URL for same-origin requests, nothing for cross-origin | When you need referrer for internal navigation only |
no-referrer-when-downgrade |
Full URL unless navigating from HTTPS to HTTP | Browser default; not restrictive enough for most sites |
Configuration Examples
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
Header always set Referrer-Policy "strict-origin-when-cross-origin"
import helmet from 'helmet';
app.use(helmet.referrerPolicy({
policy: 'strict-origin-when-cross-origin',
}));
Permissions-Policy
Permissions-Policy (formerly Feature-Policy) lets you explicitly disable browser APIs that your site does not use. If your page never needs the camera, microphone, geolocation, or payment API, you should disable them. This way, even if an attacker injects a script, they cannot access these sensitive features.
Common Directives
camera=()— Disables camera access entirely.microphone=()— Disables microphone access.geolocation=()— Disables geolocation API.payment=()— Disables Payment Request API.usb=()— Disables WebUSB API.interest-cohort=()— Opts out of Google's Federated Learning of Cohorts (FLoC) tracking.autoplay=(self)— Only allows autoplay from your own origin.
The empty parentheses () mean "no one is allowed, not even this page." Use (self) when your own page needs the feature but third-party iframes should not have access.
Configuration Examples
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()" always;
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()"
// Helmet does not cover Permissions-Policy fully yet.
// Set it manually as middleware:
app.use((req, res, next) => {
res.setHeader('Permissions-Policy',
'camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()'
);
next();
});
Putting It All Together
Here are complete, production-ready configurations with all six headers combined. Copy the one matching your stack and adjust the CSP sources to match your site.
server {
listen 443 ssl http2;
server_name example.com;
# --- Security Headers ---
# Content Security Policy
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests;" always;
# HSTS (1 year + preload)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Clickjacking protection
add_header X-Frame-Options "DENY" always;
# MIME type sniffing protection
add_header X-Content-Type-Options "nosniff" always;
# Referrer control
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Feature restrictions
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()" always;
# Hide server version
server_tokens off;
}
# --- Security Headers ---
<IfModule mod_headers.c>
# Content Security Policy
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' https://fonts.gstatic.com; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests;"
# HSTS (1 year + preload)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Clickjacking protection
Header always set X-Frame-Options "DENY"
# MIME type sniffing protection
Header always set X-Content-Type-Options "nosniff"
# Referrer control
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Feature restrictions
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()"
</IfModule>
# Hide server signature
ServerSignature Off
import helmet from 'helmet';
import express from 'express';
const app = express();
// Helmet covers most headers out of the box
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
connectSrc: ["'self'"],
frameAncestors: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
upgradeInsecureRequests: [],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
frameguard: { action: 'deny' },
noSniff: true,
referrerPolicy: {
policy: 'strict-origin-when-cross-origin',
},
}));
// Permissions-Policy (not fully covered by Helmet)
app.use((req, res, next) => {
res.setHeader('Permissions-Policy',
'camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()'
);
next();
});
app.listen(3000);
Generate Headers Without Memorizing Syntax
NexTool has free tools that output production-ready security header configurations for your server.
CSP Header Generator .htaccess GeneratorTesting Your Headers
Deploying headers without verifying them is like writing code without running it. Here is how to confirm your headers are working correctly.
Method 1: Browser DevTools
Open DevTools (F12), go to the Network tab, reload the page, click the document request, and inspect the Response Headers section. Every header you configured should appear there.
Method 2: Command Line
# Show response headers only
curl -I https://example.com
# Show specific header
curl -sI https://example.com | grep -i "content-security-policy"
curl -sI https://example.com | grep -i "strict-transport-security"
# Show all security-relevant headers
curl -sI https://example.com | grep -iE "(content-security|strict-transport|x-frame|x-content-type|referrer-policy|permissions-policy)"
Method 3: Automated Scanning
Use NexTool's HTTP Header Analyzer to inspect all response headers from any URL. It highlights missing security headers and flags potential misconfigurations. For an external benchmark, securityheaders.com grades your site from A+ to F and provides actionable recommendations.
Method 4: CSP Violation Monitoring
For Content Security Policy specifically, the report-uri and report-to directives let you collect violation reports from real user browsers. This is critical during the CSP rollout phase to catch legitimate resources you forgot to whitelist:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-uri /api/csp-report; report-to csp-endpoint
If you are adding headers to an existing site: start with X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and Permissions-Policy (these rarely break anything). Then add HSTS with a short max-age. Finally, deploy CSP in report-only mode, fix violations, and switch to enforcement. This order minimizes the risk of breaking your production site.
Related NexTool Tools
These free tools help you generate, test, and debug your HTTP security headers:
Frequently Asked Questions
Every website should set at least six HTTP security headers: Content-Security-Policy (CSP) to prevent XSS and code injection, Strict-Transport-Security (HSTS) to enforce HTTPS connections, X-Frame-Options set to DENY or SAMEORIGIN to block clickjacking, X-Content-Type-Options set to nosniff to stop MIME-type sniffing, Referrer-Policy to control what URL information is shared with other sites, and Permissions-Policy to disable browser features your site does not use such as camera, microphone, and geolocation.
A Content Security Policy (CSP) is an HTTP response header that tells the browser which sources of content (scripts, styles, images, fonts, etc.) are allowed to load on your page. It matters because it is the single most effective defense against Cross-Site Scripting (XSS) attacks. A strict CSP blocks inline scripts and restricts external script sources, so even if an attacker finds an injection point, the browser refuses to execute the malicious code. Start with a report-only policy, monitor violations, then enforce it.
HSTS (HTTP Strict Transport Security) is configured by adding the Strict-Transport-Security header to your HTTPS responses. The recommended configuration is: Strict-Transport-Security: max-age=31536000; includeSubDomains; preload. The max-age of 31536000 seconds equals one year. The includeSubDomains directive applies HSTS to all subdomains. The preload directive allows your domain to be included in browser preload lists, which enforce HTTPS even on the very first visit. Before enabling HSTS, make sure your entire site works over HTTPS because the header cannot be easily revoked once browsers cache it.
The CSP frame-ancestors directive is the modern replacement for X-Frame-Options and offers more flexibility (you can whitelist specific domains, not just same-origin). However, you should still include X-Frame-Options as a fallback because some older browsers do not fully support CSP frame-ancestors. Setting both X-Frame-Options: SAMEORIGIN and Content-Security-Policy: frame-ancestors 'self' provides the widest browser coverage against clickjacking attacks.
You can test your HTTP security headers in several ways. Use NexTool's free HTTP Header Analyzer to inspect all response headers from any URL. Browser DevTools (Network tab) let you examine headers on individual requests. The securityheaders.com scanner grades your site from A+ to F based on header presence and configuration. For CSP specifically, deploy in Content-Security-Policy-Report-Only mode first and monitor the browser console or a reporting endpoint for violations before switching to enforcement mode.