HTTP Security Headers: Complete Guide to Protecting Your Website

Every response your server sends is a chance to instruct the browser how to protect your users. Here is exactly how to configure the six headers that matter most — with copy-paste examples for Apache, Nginx, and Express.js.

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:

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

Configuration Examples

nginx — Content Security Policy
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;
apache — .htaccess Content Security Policy
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;"
express.js — Content Security Policy with Helmet
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: [],
  },
}));
Start with Report-Only

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

Caution Before Enabling

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

nginx — HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
apache — .htaccess HSTS
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
express.js — HSTS
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

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

nginx — X-Frame-Options
add_header X-Frame-Options "DENY" always;
apache — .htaccess X-Frame-Options
Header always set X-Frame-Options "DENY"
express.js — X-Frame-Options
import helmet from 'helmet';

// Helmet sets X-Frame-Options to SAMEORIGIN by default
app.use(helmet.frameguard({ action: 'deny' }));
X-Frame-Options vs. CSP frame-ancestors

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

nginx — X-Content-Type-Options
add_header X-Content-Type-Options "nosniff" always;
apache — .htaccess X-Content-Type-Options
Header always set X-Content-Type-Options "nosniff"
express.js — X-Content-Type-Options
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

nginx — Referrer-Policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
apache — .htaccess Referrer-Policy
Header always set Referrer-Policy "strict-origin-when-cross-origin"
express.js — Referrer-Policy
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

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

nginx — Permissions-Policy
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()" always;
apache — .htaccess Permissions-Policy
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()"
express.js — Permissions-Policy
// 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.

nginx — All Security Headers
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;
}
apache — .htaccess All Security Headers
# --- 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
express.js — All Security Headers
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 Generator

Testing 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

bash — Inspect Headers with curl
# 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:

http — CSP with Reporting
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-uri /api/csp-report; report-to csp-endpoint
Deployment Order

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.

NT

NexTool Team

We build free, browser-based developer tools and write technical guides focused on web security, performance, and best practices.

Audit Your Headers Right Now

Paste any URL into the HTTP Header Analyzer and see exactly which security headers you are missing.

Analyze Headers Free