Security Headers for Web Applications: The Complete Configuration Guide
Configure HTTP security headers correctly — CSP, HSTS, X-Frame-Options, Permissions-Policy, and every header that protects your web application from common attacks.

James Ross Jr.
Strategic Systems Architect & Enterprise Software Developer
Security Headers for Web Applications: The Complete Configuration Guide
HTTP security headers are browser instructions about how your application should be handled. They provide defense against cross-site scripting, clickjacking, MIME sniffing, and a range of other attacks — without a single line of application logic change. They are added to HTTP responses, they are free, and most applications are not configured with them correctly.
I am going to go through every meaningful security header, what it does, and exactly how to configure it. At the end, you will have a complete configuration you can deploy.
Content-Security-Policy
CSP is the most powerful and most complex security header. It tells the browser which resources (scripts, styles, images, fonts, API calls) are allowed to load on your page. A well-configured CSP prevents XSS from executing even if an attacker successfully injects script content.
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.yourdomain.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https:;
connect-src 'self' https://api.yourdomain.com;
media-src 'none';
object-src 'none';
frame-src 'none';
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
upgrade-insecure-requests;
Let me explain each directive:
default-src 'self' — the fallback for any resource type not explicitly listed. Only allow resources from your own origin.
script-src 'self' https://cdn.yourdomain.com — only execute scripts from your origin and your CDN. This blocks inline scripts and scripts from unknown domains. No 'unsafe-inline' unless absolutely necessary.
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com — inline styles are common in modern frameworks (styled-components, emotion, inline Tailwind), so 'unsafe-inline' for styles is often necessary. This does not create an XSS vulnerability because CSS cannot execute JavaScript (with rare exceptions that are covered by other directives).
object-src 'none' — disable Flash, Java applets, and other plugins entirely. There is no legitimate reason to enable these in 2026.
frame-ancestors 'none' — equivalent to X-Frame-Options: DENY. Your page cannot be embedded in any iframe. Prevents clickjacking.
base-uri 'self' — restricts <base> tag targets. Prevents base tag injection attacks where attackers change the base URL for relative links.
form-action 'self' — forms can only submit to your own origin. Prevents form hijacking attacks.
upgrade-insecure-requests — automatically upgrades HTTP URLs on your page to HTTPS. Fixes mixed content issues.
Start with CSP in report-only mode:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /api/csp-violations
This logs violations without blocking anything. Review the report logs, adjust your policy to allow legitimate resources, then switch to enforcement mode.
Strict-Transport-Security (HSTS)
Forces HTTPS for all connections to your domain. Once a browser sees this header, it remembers to use HTTPS for your domain for the specified duration without any HTTP round trip.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age=31536000 — remember for one year.
includeSubDomains — apply to all subdomains.
preload — signal readiness for browser preload lists. Submit at hstspreload.org to get included.
Start with a short max-age (300 seconds), verify your entire site works over HTTPS, then increase to one year. HSTS with broken HTTPS means users cannot reach your site.
X-Frame-Options
Older equivalent to CSP's frame-ancestors. Controls iframe embedding:
X-Frame-Options: DENY
DENY — your page cannot be embedded anywhere.
SAMEORIGIN — can be embedded by your own domain only.
Keep this header even if you have frame-ancestors in your CSP for compatibility with older browsers.
X-Content-Type-Options
Prevents browsers from "sniffing" content types. Without this header, a browser might execute a JavaScript file served with a misleading content type:
X-Content-Type-Options: nosniff
Always include this. It has no side effects on correctly configured servers and prevents a class of file upload and content injection attacks.
Referrer-Policy
Controls how much information is included in the Referer header sent with outgoing requests. Without this header, clicking a link from your page to an external site sends your full URL to that external site, including query parameters that might contain user IDs, session tokens, or private data.
Referrer-Policy: strict-origin-when-cross-origin
strict-origin-when-cross-origin — for same-origin requests, send the full URL. For cross-origin requests, send only the origin (no path or query string). For cross-origin requests to a less-secure scheme (HTTPS to HTTP), send nothing.
This is the recommended value for most applications. It preserves referrer analytics for same-origin navigation while protecting sensitive URL parameters from leaking to third parties.
Permissions-Policy
Formerly Feature-Policy. Restricts access to browser APIs and features. Disable everything your application does not use:
Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=(), payment=(), usb=(), accelerometer=(), gyroscope=(), magnetometer=()
Each empty () disables the feature entirely. Enabling features for your origin: camera=(self). Enabling for a specific external origin: camera=("https://video.yourdomain.com").
Note: interest-cohort=() disables FLoC (Google's Federated Learning of Cohorts), which uses your site to profile users for ad targeting. This opt-out is good practice regardless of your security posture.
Cross-Origin Headers
Three newer headers that affect how your resources are shared across origins. These matter for isolation and Spectre mitigation:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-origin
COOP: same-origin — prevents other origins from accessing your window via window.opener. Provides isolation from cross-origin popups.
COEP: require-corp — requires all resources loaded by your page to opt in to cross-origin sharing. Combined with COOP, enables access to SharedArrayBuffer (needed for high-performance web applications using WebAssembly) and provides Spectre mitigations.
These headers can break third-party embeds if those resources do not send appropriate Cross-Origin-Resource-Policy headers. Implement with testing.
Implementation in Node.js with Helmet
The helmet middleware for Express sets all these headers with sensible defaults:
import helmet from "helmet";
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://cdn.yourdomain.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.yourdomain.com"],
frameSrc: ["'none'"],
objectSrc: ["'none'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
referrerPolicy: { policy: "strict-origin-when-cross-origin" },
frameguard: { action: "deny" },
noSniff: true,
})
);
Helmet covers the major headers. Add Permissions-Policy manually since Helmet does not include it by default:
app.use((req, res, next) => {
res.setHeader(
"Permissions-Policy",
"camera=(), microphone=(), geolocation=(), interest-cohort=()"
);
next();
});
Implementation in Nginx
Set headers at the server block level for all responses:
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none';" always;
add_header Strict-Transport-Security "max-age=31536000; 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;
The always directive ensures headers are sent even for error responses.
Verification
Test your security headers using:
- securityheaders.com — grades your header configuration and identifies missing or misconfigured headers
- Mozilla Observatory (observatory.mozilla.org) — comprehensive analysis including TLS configuration
- SSL Labs (ssllabs.com/ssltest) — TLS-specific analysis
Aim for A or A+ on Security Headers and A on Mozilla Observatory. Run these tests after initial deployment and after any significant configuration change.
If you want help configuring security headers for your web application or reviewing your current security header configuration, book a session at https://calendly.com/jamesrossjr.