Security Headers Overview

Essential HTTP response headers that protect your site against clickjacking, MIME sniffing, information leakage, and unwanted browser features.

X-Frame-Options

X-Frame-Options controls whether a browser allows your page to be embedded in an <iframe>, <frame>, <embed>, or <object>. This prevents clickjacking attacks where an attacker overlays your page with transparent UI elements to trick users into clicking hidden buttons.

Two values are supported:

X-Frame-Options: DENY

Deprecation note: X-Frame-Options is superseded by CSP's frame-ancestors directive, which is more flexible (supports multiple origins, wildcards, and scheme restrictions). However, X-Frame-Options should still be set for compatibility with older browsers that do not support CSP Level 2. When both are present, frame-ancestors takes precedence in supporting browsers.

# CSP equivalent of X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'

# CSP equivalent of X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'

# CSP can also allow specific origins (X-Frame-Options cannot)
Content-Security-Policy: frame-ancestors 'self' https://trusted.example.com

X-Content-Type-Options

The only valid value is nosniff:

X-Content-Type-Options: nosniff

This header prevents browsers from MIME-sniffing a response away from the declared Content-Type. Without it, a browser might interpret a file served as text/plain as HTML or JavaScript if the content looks like code -- enabling XSS attacks through file uploads or user-generated content.

For script and style resources, nosniff causes the browser to block the resource entirely if the MIME type does not match (text/javascript for scripts, text/css for stylesheets). This prevents an attacker from serving a malicious script disguised with an innocent Content-Type.

Set this header on every response. There is no downside and no reason to omit it.

Referrer-Policy

Controls how much referrer information is included in the Referer header when navigating away from your page. The referrer can leak sensitive URLs (query parameters with tokens, internal paths) to third-party sites.

Common values, from most restrictive to least:

Referrer-Policy: strict-origin-when-cross-origin

Modern browsers (Chrome 85+, Firefox 87+) already default to strict-origin-when-cross-origin, but setting it explicitly ensures consistent behavior across all browsers and clearly documents your intent.

Permissions-Policy

Formerly known as Feature-Policy, Permissions-Policy controls which browser features and APIs are available to your page and any embedded iframes. This reduces your attack surface by disabling APIs you do not use.

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

Each directive names a feature and specifies an allowlist of origins. An empty allowlist () disables the feature entirely. Common features to restrict:

You can also grant features to specific origins:

# Allow camera only for your own origin and a trusted video provider
Permissions-Policy: camera=(self "https://video.example.com"), microphone=()

Recommended baseline header set

Every production web server should send at minimum these security headers on every response:

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Content-Security-Policy: default-src 'self'; frame-ancestors 'none'; object-src 'none'

This baseline provides HTTPS enforcement, click-jacking prevention, MIME sniffing protection, referrer privacy, feature restriction, and a basic CSP. Adjust the CSP directives based on your application's actual resource needs.

Server configuration examples

Nginx

# /etc/nginx/snippets/security-headers.conf
# Include this in your server or location blocks

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
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 Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'none'; object-src 'none'; base-uri 'self'" always;

server {
    listen 443 ssl http2;
    server_name example.com;

    include snippets/security-headers.conf;

    # ... rest of server config
}

The always keyword ensures headers are sent on error responses too (4xx, 5xx), not just successful ones.

Apache

# httpd.conf or .htaccess (requires mod_headers)
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"
Header always set Content-Security-Policy "default-src 'self'; frame-ancestors 'none'; object-src 'none'; base-uri 'self'"

Traefik

# traefik dynamic configuration
http:
  middlewares:
    security-headers:
      headers:
        stsSeconds: 63072000
        stsIncludeSubdomains: true
        stsPreload: true
        contentTypeNosniff: true
        frameDeny: true
        referrerPolicy: "strict-origin-when-cross-origin"
        permissionsPolicy: "camera=(), microphone=(), geolocation=(), payment=()"
        contentSecurityPolicy: "default-src 'self'; frame-ancestors 'none'; object-src 'none'; base-uri 'self'"

Apply the middleware to your routers:

# Docker label example
- "traefik.http.routers.myapp.middlewares=security-headers@file"

Check your site's HTTP headers with spectra