Why Security Headers Matter

HTTP security headers tell the browser how to handle your site's content safely. Configured correctly, they shut down a whole class of attacks: an attacker plants a script in the comments section and the browser refuses to run it because of Content-Security-Policy; a third-party site tries to embed your login page in an iframe and X-Frame-Options blocks it; an image turns out to be HTML and X-Content-Type-Options: nosniff stops the browser from second-guessing the content type.

None of these headers slow down your site or require touching business code — the cost is essentially zero, and the payoff is a shrunken attack surface. Below are complete Nginx and Apache configurations plus an explanation of each header.

Complete Nginx Security Header Configuration

# /etc/nginx/nginx.conf or the server block of your site config

# === Core security headers ===

# Prevent clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;

# Prevent MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;

# Legacy XSS filter (deprecated, kept for compatibility)
add_header X-XSS-Protection "0" always;

# Control referrer information
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Permission control (restrict browser APIs)
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;

# === HSTS (HTTP Strict Transport Security) ===
# Enable only after HTTPS is confirmed working
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

# === CSP (Content Security Policy) ===
# Tune to your needs; too strict can break functionality
add_header Content-Security-Policy "
  default-src 'self';
  script-src 'self' 'unsafe-inline' 'unsafe-eval' https:;
  style-src 'self' 'unsafe-inline' https:;
  img-src 'self' data: https:;
  font-src 'self' data: https:;
  connect-src 'self' https:;
  frame-ancestors 'self';
  form-action 'self';
  base-uri 'self';
" always;

Apache Configuration

# .htaccess or httpd.conf

# Core security headers
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

Header Reference

Header Purpose Recommended Value Risk
X-Frame-Options Prevent clickjacking (page embedded in an iframe) SAMEORIGIN Low
X-Content-Type-Options Prevent MIME sniffing attacks nosniff Low
Strict-Transport-Security Enforce HTTPS connections max-age=63072000; includeSubDomains Medium
Referrer-Policy Control the HTTP Referer header strict-origin-when-cross-origin Low
Permissions-Policy Restrict browser API access Restrict per need Low
Content-Security-Policy Prevent XSS and data injection Site-specific High

What each header really does

X-Frame-Options is the oldest clickjacking defense: SAMEORIGIN only lets same-origin pages embed yours in an iframe. Modern browsers also support the frame-ancestors directive (inside CSP); when both exist, CSP wins — so set both.

Strict-Transport-Security (HSTS) tells the browser "this domain must only ever be reached over HTTPS," eliminating the downgrade risk on the very first HTTP request. max-age=63072000 is 730 days, includeSubDomains covers subdomains, and preload submits the domain to the browser's built-in HSTS preload list. Caution: once HSTS is live, the browser forces HTTPS — if a subdomain hasn't got a certificate yet, users simply can't reach it. That's why it's rated medium risk.

Referrer-Policy controls how much referrer information leaks when a page navigates. strict-origin-when-cross-origin is the default sweet spot: full URL on same-origin, only the origin cross-origin, and nothing when going from HTTPS to HTTP.

Permissions-Policy replaces the old Feature-Policy and restricts browser APIs like camera, microphone, and geolocation. Setting unused capabilities to empty shrinks what malicious scripts can reach.

Content-Security-Policy is the most complex and most powerful header, and the one most likely to break your page. It declares resource sources per directive: script-src for scripts, style-src for styles, img-src for images, connect-src for fetch/XHR. Being too strict nukes inline scripts, CDN assets, and third-party analytics alike — so always "report first, tighten later."

Order matters when rolling out, too: enable everything in staging first, watch for a week of regressions and console errors, then roll out to production gradually. Once a header is sent, the browser remembers state like HSTS, so rollback isn't instant — "small steps, progressively stricter" beats a one-shot lockdown. With CSP in particular, leave a transition period in Content-Security-Policy-Report-Only mode to collect violation reports and see which resources real pages actually trigger, then decide which domains deserve whitelisting.

CSP Configuration Guide

Relaxed policy (suits most sites)

add_header Content-Security-Policy "
  default-src 'self';
  script-src 'self' 'unsafe-inline' https:;
  style-src 'self' 'unsafe-inline' https:;
  img-src 'self' data: https:;
  font-src 'self' data: https:;
  connect-src 'self' https:;
  frame-ancestors 'self';
  form-action 'self';
" always;

Strict policy (for security-sensitive sites)

add_header Content-Security-Policy "
  default-src 'none';
  script-src 'self';
  style-src 'self';
  img-src 'self';
  connect-src 'self';
  frame-ancestors 'none';
  form-action 'self';
  base-uri 'self';
" always;

Verification Tools

  • securityheaders.com — Header rating
  • CSP Evaluator — CSP policy assessment
  • SSL Labs — SSL/TLS configuration rating
  • curl -sI https://example.com | grep -i "^\(x-\|strict\|content\|referrer\|permissions\)" — local check

Verifying After Configuration

  1. Confirm every header is returned: curl -sI https://your-domain.com | grep -i "content-security\|strict-transport\|x-frame\|x-content-type\|referrer-policy\|permissions-policy";
  2. Open securityheaders.com, enter the domain, and check the grade is A or A+;
  3. Paste your CSP into CSP Evaluator and look for obvious holes (like mixing 'unsafe-inline' with external scripts);
  4. Finally walk through your main pages, login flow, and payment flow in a real browser to make sure nothing broke.

FAQ and Troubleshooting

Subdomains unreachable after enabling HSTS? Make sure each subdomain has a valid HTTPS certificate before adding includeSubDomains. In an emergency, removing HSTS won't clear clients' cached enforcement until max-age expires — be extra careful with production changes.

CSP too strict and the page styles are a mess? Switch back to the relaxed policy, or use report-uri/report-to to collect violation reports first, see which resources are blocked, whitelist them one by one, then tighten.

Headers set but curl doesn't show them? Check whether Nginx's add_header lives inside a location block — add_header in location overrides the server-level header of the same name. On Apache, confirm mod_headers is enabled.

A Real-World Case

A content site kept getting injected with malicious scripts: attackers stuffed <script src="https://evil.example/x.js"> into article bodies. After enabling a strict CSP (script-src 'self'), external scripts were refused outright; combined with X-Content-Type-Options: nosniff and Referrer-Policy, the injection attack surface effectively closed. The whole change took about an hour with zero production incidents.

Configuration Checklist

  • X-Frame-Options: SAMEORIGIN
  • X-Content-Type-Options: nosniff
  • Strict-Transport-Security configured
  • Referrer-Policy configured
  • Permissions-Policy configured
  • Content-Security-Policy configured and tested
  • securityheaders.com grade A or A+
  • Site functionality verified after configuration