Dev Tools

HTTP Security Headers: The Complete Developer Guide for 2026

Missing security headers are one of the most common and easily fixed web vulnerabilities. Here's what each one does and how to implement them correctly.

📅 Jun 2, 2026·⏱️ 10 min read·✍️ Cikal Studio Labs
🧱

Why Security Headers Are Your Most Efficient Security Investment

HTTP security headers are server-side configuration settings that instruct browsers to enable specific security behaviors — restricting which origins can load content, preventing specific attack types, and controlling how browsers interact with your application. They require no code changes to your application logic, take minutes to implement for experienced developers, and protect against a wide range of real-world attacks. Missing security headers consistently appear in the OWASP Top 10 most critical web vulnerabilities — not because they're exotic, but because they're so commonly overlooked.

The cost-benefit ratio of security headers is extraordinary: a few lines of configuration protects against XSS attacks, clickjacking, MIME sniffing, protocol downgrade attacks, and data leakage from external resources. Every web application should have a complete security header stack. This guide covers each header in detail — what it does, why it matters, and exactly how to implement it.

Content Security Policy (CSP): The Most Powerful and Most Complex

CSP is the single most powerful security header available. It provides a whitelist of approved content sources — telling the browser exactly where scripts, styles, images, fonts, frames, and other resources are allowed to load from. Any resource from an unlisted source is blocked. This fundamentally prevents:

  • Cross-site scripting (XSS): Even if an attacker injects a <script> tag, the browser blocks it if the source isn't in the CSP whitelist. Inline scripts (without nonces) are blocked by default with strict CSP.
  • Data exfiltration: connect-src limits which servers JavaScript can make network requests to. An injected script can't exfiltrate data to an attacker's server if that server isn't in the connect-src whitelist.
  • Clickjacking: frame-ancestors 'none' prevents your page from being embedded in iframes on other domains — the primary mechanism for clickjacking attacks.

A strict CSP for a typical web application:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{RANDOM}'; style-src 'self' 'nonce-{RANDOM}'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';

The nonce-{RANDOM} approach generates a unique random value per request and includes it in approved inline scripts, allowing specific inline scripts while blocking all others. This is significantly more secure than 'unsafe-inline' which allows all inline scripts.

HTTP Strict Transport Security (HSTS)

HSTS prevents SSL stripping attacks — where an attacker on your network intercepts the initial HTTP request before your server redirects to HTTPS. HSTS tells browsers: for this domain, always use HTTPS, even if the user types plain HTTP or clicks an HTTP link. Once a browser has seen the HSTS header, it enforces HTTPS for subsequent visits without making an initial HTTP request that could be intercepted.

Implementation: Strict-Transport-Security: max-age=31536000; includeSubDomains

Add preload directive and submit to hstspreload.org to have your domain hardcoded into browsers' built-in HSTS lists — the strongest protection, effective from the very first visit even before any HSTS header has been received.

X-Frame-Options: Preventing Clickjacking

Clickjacking attacks embed your legitimate website in an invisible iframe over a malicious page, then trick users into clicking your interface elements while believing they're clicking the malicious page. This can be used to trigger actions the user doesn't intend — clicking "Confirm" on a bank transfer, enabling camera access, or approving OAuth permissions.

X-Frame-Options: DENY — prevents your page from being embedded in any iframe anywhere.
X-Frame-Options: SAMEORIGIN — allows embedding only on pages from the same origin as your site.

Note: X-Frame-Options is largely superseded by CSP's frame-ancestors directive, which provides more granular control. However, X-Frame-Options is still needed for older browsers that don't support CSP. Implement both for complete coverage.

X-Content-Type-Options: Preventing MIME Sniffing

MIME sniffing is a browser behavior where the browser tries to determine a resource's content type by examining its contents, sometimes overriding the server-declared Content-Type header. This creates a vulnerability: if an attacker can upload a JavaScript file to your server disguised as an image, the browser might execute it as JavaScript despite the server declaring it as an image.

X-Content-Type-Options: nosniff

With this header, browsers strictly honor the server-declared Content-Type and refuse to "sniff" or guess the type. This is a simple, one-line addition with no breaking changes for correctly configured applications and significant security benefit.

Referrer-Policy: Controlling Information Leakage

The Referrer header is automatically sent with requests, telling the destination server which page the user came from. URL parameters sometimes contain sensitive information (session tokens, user IDs, search terms, form data), and the Referrer header can inadvertently expose this to third parties when users click external links.

Referrer-Policy: strict-origin-when-cross-origin (recommended for most sites)

This policy sends the full URL as referrer for same-origin requests (useful for internal analytics), but only the origin (domain, no path) for cross-origin requests — preventing path-level information leakage to external sites while maintaining same-origin referrer data for your own analytics.

Permissions-Policy (formerly Feature-Policy)

Permissions-Policy controls which browser features and APIs the page is allowed to use. This limits the damage that a successful XSS attack or malicious third-party script can do by restricting access to sensitive capabilities your application doesn't actually need:

Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=(), payment=(), usb=()

Empty parentheses mean the feature is disabled for this origin and all embedded iframes. This means even if an attacker injects script that tries to access the camera, the browser refuses — the policy prevents it at the browser level, not just at the application level.

Cross-Origin Headers: COOP, CORP, and COEP

The trio of cross-origin headers provides isolation between browsing contexts — critical for preventing Spectre-class side-channel attacks that exploit CPU-level vulnerabilities to read cross-origin memory:

  • Cross-Origin-Opener-Policy (COOP): same-origin — isolates your page's browsing context from cross-origin windows, preventing cross-window attacks and enabling access to high-precision timers required for SharedArrayBuffer.
  • Cross-Origin-Embedder-Policy (COEP): require-corp — requires all sub-resources to explicitly opt in to being loaded cross-origin. Enables use of SharedArrayBuffer and high-resolution performance timers.
  • Cross-Origin-Resource-Policy (CORP): same-origin or same-site — prevents other origins from loading your resources, protecting sensitive assets from cross-origin data leakage attacks.

Platform-Specific Implementation

Security header implementation varies by platform but follows the same principles:

  • Next.js: In next.config.ts, use the headers() async function returning an array of header objects. Apply to all routes with source: '/(.*)'.
  • Nginx: Add add_header directives to server or location blocks. Use always parameter to add headers to error responses as well as success responses.
  • Apache: Use Header always set directives in httpd.conf, .htaccess, or VirtualHost configuration.
  • Express.js: Use the Helmet middleware package, which configures all standard security headers with sensible defaults: app.use(helmet()).
💡 Implementation sequence: Start with the easy, zero-breaking-change headers first — X-Content-Type-Options, X-Frame-Options, Referrer-Policy. These take five minutes and have no side effects. Implement HSTS next (start with a short max-age to test). Save CSP for last since it requires auditing all content sources in your application to build a correct whitelist without breaking functionality.

Continuous Security Header Monitoring

Security headers can be inadvertently removed by infrastructure changes — a CDN configuration update, a reverse proxy configuration change, a new deployment pipeline, or an emergency rollback that restores an older configuration without the headers. Automated monitoring that verifies the presence and correct values of all security headers on every deployment — and alerts on any missing or changed header — ensures that security regressions are caught immediately rather than discovered during a security audit months later. Security header validation should be a standard step in your CI/CD pipeline, treated with the same seriousness as functional test coverage. A header that was correctly configured last week but is missing today represents an active security regression that needs immediate remediation.

Frequently Asked Questions

If I can only add one security header today, which one matters most?

Start with the low-effort, zero-breaking-change headers first: X-Content-Type-Options, X-Frame-Options, and Referrer-Policy each take about five minutes to add and have no side effects. Content-Security-Policy is the most powerful header overall — it can block XSS, data exfiltration, and clickjacking in one shot — but save it for last since it requires auditing every content source your app actually uses before you can write a whitelist that doesn't break functionality.

What does Content-Security-Policy actually stop an attacker from doing?

CSP defines a whitelist of approved sources for scripts, styles, images, and other resources, and the browser blocks anything not on that list. In practice this stops cross-site scripting even when an attacker successfully injects a script tag (it won't load from an unlisted source), blocks data exfiltration by restricting connect-src to servers you actually call, and prevents clickjacking via frame-ancestors 'none'. Using per-request nonces for approved inline scripts is significantly safer than the common shortcut of 'unsafe-inline', which allows any inline script to run.

How do I add security headers to a Next.js application?

In next.config.ts, export an async headers() function that returns an array of header objects, applying a base set to every route with source: '/(.*)' and overriding specific routes as needed. This is the standard mechanism for Next.js — for other stacks, Express apps typically use the Helmet middleware (app.use(helmet())), Nginx uses add_header directives with the always parameter, and Apache uses Header always set.

Why did our security headers suddenly disappear after a deployment?

Security headers are easy to lose silently through infrastructure changes — a CDN configuration update, a reverse proxy change, a new deploy pipeline, or an emergency rollback to an older configuration that predates the headers. Because there's no functional breakage when a header is missing, this kind of regression can go unnoticed for months. Treating security header validation as a standard automated CI/CD check, the same way you'd treat a failing test, catches the regression the moment it happens instead of during the next audit.

Is there a tool that checks which security headers my site is missing?

Yes — an API Security Headers Auditor can scan a live URL and report exactly which of the standard headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and the COOP/COEP/CORP trio) are present, missing, or misconfigured, without you having to manually inspect response headers for every route.