← All Articles · · 12 min read

HTTP Headers Explained: Security & Performance (2025)

A developer's guide to HTTP headers. Covers security headers (HSTS, CSP, CORS), performance headers (Cache-Control, ETag), and how to inspect headers with free tools.

httpheaderssecurityperformancecorscachingweb-development

HTTP headers are the invisible metadata that travel with every request and response. They control security, caching, content negotiation, authentication, and performance. Getting them right is the difference between a fast, secure website and one that’s vulnerable to common attacks.

This guide explains the headers that matter most — organized by category, with practical examples you can deploy today.


Inspecting Headers

Before optimizing, you need to see what’s there. Use the DevPlaybook HTTP Headers Inspector to check any URL instantly, or use the command line:

# View all response headers
curl -I https://example.com

# Include headers + body
curl -i https://example.com

# Specific header (case-insensitive)
curl -sI https://example.com | grep -i cache-control

# Full verbose output (request + response)
curl -v https://example.com 2>&1 | grep -E "^[<>]"

In the browser: DevTools → Network tab → click any request → Headers tab.


Security Headers

Security headers protect your users from common web attacks. None of these require code changes — they’re HTTP response headers your server or CDN sends.

1. Strict-Transport-Security (HSTS)

Forces browsers to use HTTPS, preventing protocol downgrade attacks and cookie hijacking over HTTP.

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age=31536000: Cache this directive for one year
  • includeSubDomains: Apply to all subdomains (required for preload)
  • preload: Submit to the HSTS preload list (browsers load this before the first request)

Warning: Only add preload if you’re certain all subdomains serve HTTPS. It’s very hard to reverse.

Nginx:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

2. Content-Security-Policy (CSP)

The most powerful security header. Defines which sources can load scripts, styles, images, and other resources. Blocks XSS attacks at the browser level.

Strict policy (SPAs):

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'

With CDN and analytics:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.jsdelivr.net https://www.googletagmanager.com;
  style-src 'self' https://fonts.googleapis.com 'unsafe-inline';
  font-src 'self' https://fonts.gstatic.com;
  img-src 'self' data: https:;
  connect-src 'self' https://api.example.com;
  frame-src 'none';
  object-src 'none'

Start with report-only mode to avoid breaking things:

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

This logs violations without blocking them, so you can audit before enforcing.

3. X-Frame-Options

Prevents your site from being embedded in iframes — blocks clickjacking attacks.

X-Frame-Options: DENY           # No iframes at all
X-Frame-Options: SAMEORIGIN     # Only same-origin iframes

Note: CSP’s frame-ancestors directive is more flexible and supersedes this header. Use both for maximum compatibility:

X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'

4. X-Content-Type-Options

Prevents browsers from MIME-sniffing responses away from the declared Content-Type. This stops attacks that disguise malicious files as innocent ones.

X-Content-Type-Options: nosniff

Always set this. There’s no reason not to.

5. Referrer-Policy

Controls how much referrer information is sent with requests. Protects user privacy and prevents leaking internal URLs.

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

Options from most to least private:

  • no-referrer — never send referrer
  • no-referrer-when-downgrade — don’t send on HTTPS→HTTP
  • strict-origin — only send origin (no path/query)
  • strict-origin-when-cross-origin — full URL on same-origin, origin only cross-origin (recommended)
  • unsafe-url — always send full URL (never use this)

6. Permissions-Policy

Restricts browser features your site doesn’t use. Prevents third-party scripts from accessing the camera, microphone, or geolocation.

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

This says: no access to camera, microphone, geolocation, or payment APIs from any frame.

7. Cross-Origin Resource Sharing (CORS)

CORS headers allow (or deny) browsers from making cross-origin requests to your API.

# Allow specific origins
Access-Control-Allow-Origin: https://app.example.com

# Allow credentials (cookies, auth headers)
Access-Control-Allow-Credentials: true

# Allowed methods
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS

# Allowed headers
Access-Control-Allow-Headers: Content-Type, Authorization, X-Request-ID

# Cache preflight for 24 hours
Access-Control-Max-Age: 86400

Never use Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true — this is a security error and browsers will reject it.


Performance Headers

These headers control caching behavior, content encoding, and how efficiently browsers load your resources.

Cache-Control

The most important performance header. Controls how long responses are cached by browsers and CDNs.

Common patterns:

# Cache forever (for versioned assets like main.abc123.js)
Cache-Control: public, max-age=31536000, immutable

# Revalidate every hour
Cache-Control: public, max-age=3600

# Always revalidate (but allow 304 Not Modified)
Cache-Control: no-cache

# Never cache (sensitive data, dynamic content)
Cache-Control: no-store, no-cache, must-revalidate

# Cache privately (browser only, not CDN)
Cache-Control: private, max-age=3600

Immutable tells the browser never to revalidate a cached resource — use only with content-hashed filenames where the URL changes when content changes.

ETag and Last-Modified

Enable conditional requests so clients only download content that has changed.

# Server sends:
ETag: "abc123def456"
Last-Modified: Wed, 20 Mar 2026 10:00:00 GMT

# Client revalidates with:
If-None-Match: "abc123def456"
If-Modified-Since: Wed, 20 Mar 2026 10:00:00 GMT

# Server responds with 304 Not Modified if unchanged (no body)

This is critical for large files where bandwidth matters.

Content-Encoding

Enables compression of response bodies. Modern servers and CDNs handle this automatically, but verify it’s working:

curl -H "Accept-Encoding: br,gzip" -sI https://example.com | grep content-encoding
# content-encoding: br   (Brotli — best compression)
# or:
# content-encoding: gzip (Gzip — universal support)

If your server isn’t sending Content-Encoding: br or Content-Encoding: gzip, you’re sending uncompressed responses — typically 70-80% larger than they need to be.

Vary

Tells CDNs that the response varies based on request headers, so they cache separate versions.

# Cache different versions for different Accept-Encoding values
Vary: Accept-Encoding

# Cache different versions for different origins
Vary: Origin

# Multiple variance dimensions
Vary: Accept-Encoding, Accept-Language

Content-Type

Always set explicit Content-Type headers. Omitting it forces browsers to guess:

Content-Type: application/json; charset=utf-8
Content-Type: text/html; charset=utf-8
Content-Type: image/svg+xml
Content-Type: application/javascript; charset=utf-8

Request Headers Worth Knowing

These headers are sent by clients (browsers, API consumers, mobile apps):

HeaderPurpose
AuthorizationBearer tokens, Basic auth, API keys
Content-TypeFormat of the request body
AcceptExpected response format (application/json)
Accept-EncodingCompression support (br, gzip, deflate)
Accept-LanguagePreferred response language
User-AgentClient identification
If-None-MatchConditional request using ETag
If-Modified-SinceConditional request using date
X-Request-IDCorrelation ID for distributed tracing
X-Forwarded-ForOriginal client IP (set by proxies/load balancers)

Use Mozilla Observatory or the DevPlaybook HTTP Headers Inspector to score your site:

HeaderPriority
Strict-Transport-SecurityCritical
Content-Security-PolicyCritical
X-Content-Type-Options: nosniffHigh
X-Frame-OptionsHigh
Referrer-PolicyHigh
Permissions-PolicyMedium
Cross-Origin-Opener-PolicyMedium
Cross-Origin-Resource-PolicyMedium

Setting Headers in Common Frameworks

Express.js (Node.js)

const helmet = require('helmet');

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "https://cdn.example.com"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https:"],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  },
}));

Nginx

server {
  # Security headers
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  add_header X-Content-Type-Options "nosniff" always;
  add_header X-Frame-Options "SAMEORIGIN" always;
  add_header Referrer-Policy "strict-origin-when-cross-origin" always;
  add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

  # Performance
  gzip on;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
  gzip_comp_level 6;

  location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
    add_header Cache-Control "public, max-age=31536000, immutable";
    expires 1y;
  }

  location /api/ {
    add_header Cache-Control "no-store";
  }
}

Next.js (next.config.js)

const securityHeaders = [
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
  {
    key: 'Strict-Transport-Security',
    value: 'max-age=31536000; includeSubDomains',
  },
  {
    key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline';",
  },
];

module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: securityHeaders,
      },
    ];
  },
};

Testing Your Headers

Free tools:

Quick command-line check:

# Security headers present?
curl -sI https://your-site.com | grep -iE "(strict-transport|content-security|x-frame|x-content-type|referrer)"

# Compression enabled?
curl -H "Accept-Encoding: gzip,br" -sI https://your-site.com | grep -i "content-encoding"

# CORS configured?
curl -H "Origin: https://other.com" -sI https://api.your-site.com | grep -i "access-control"

Further Reading

Related DevPlaybook tools:


Automate Your Security Checks

Want to run HTTP header security audits as part of your CI/CD pipeline? The Developer Productivity Bundle includes automated security header check scripts and GitHub Actions workflows to catch missing headers before they reach production.

Free Newsletter

Level Up Your Dev Workflow

Get new tools, guides, and productivity tips delivered to your inbox.

Plus: grab the free Developer Productivity Checklist when you subscribe.

Found this guide useful? Check out our free developer tools.