Guides

How to Fix Missing Security Headers (CSP, HSTS)

Missing security headers leave web applications vulnerable to cross-site scripting (XSS), clickjacking, protocol downgrade attacks, and credential leakage. Remediating them requires server-level response configuration and strict directive scoping.

Short answer

Deploy Strict-Transport-Security with a one-year max-age and preload, configure Content-Security-Policy with strict script and frame restrictions, enforce X-Content-Type-Options: nosniff, set frame-ancestors 'none', and specify Referrer-Policy: strict-origin-when-cross-origin.

Diagnose missing response headers

Before editing web server configurations, inspect baseline response headers using curl -IL or an automated header scanner. Identify whether the origin server, reverse proxy (such as Nginx or Traefik), or CDN edge (Cloudflare, CloudFront) terminates TLS and handles response headers. Verify that headers are not stripped by upstream proxy layers or overridden by misconfigured backend middleware.

Configure Strict-Transport-Security (HSTS) safely

HTTP Strict Transport Security forces user agents to communicate exclusively over HTTPS, preventing SSL stripping and man-in-the-middle attacks. Start with a staging test using Strict-Transport-Security: max-age=86400 (one day). Once all subdomains, assets, and APIs are confirmed reachable over valid TLS, increase to production grade: max-age=31536000; includeSubDomains; preload. Never set max-age=0 in production, as RFC 6797 defines this as an immediate instruction to delete the HSTS host pin.

Build a hardened Content Security Policy (CSP)

Content-Security-Policy restricts which domains and scripts can execute inside the browser. Start with Content-Security-Policy-Report-Only to log violations via report-to or report-uri without breaking user workflows. Define default-src 'self', script-src 'self' with dynamic nonces or SHA-256 hashes instead of 'unsafe-inline' or 'unsafe-eval', style-src 'self' 'unsafe-inline' (or hash-based), object-src 'none', base-uri 'self', and frame-ancestors 'none'. Transition from report-only to enforcement once telemetry confirms zero false-positive script rejections.

Mitigate clickjacking, MIME sniffing, and referrer leaks

Protect sensitive user sessions from UI redressing by specifying frame-ancestors 'none' (or 'self') inside CSP, backed by legacy X-Frame-Options: DENY for older clients. Prevent browsers from MIME-sniffing non-executable formats into script execution with X-Content-Type-Options: nosniff. Contain outbound referrer leakage on external links using Referrer-Policy: strict-origin-when-cross-origin, ensuring sensitive query parameters never reach third-party logs.

Server implementation and automated deployment

In Nginx, apply directives inside the HTTPS server block using add_header with the 'always' parameter (for example: add_header X-Content-Type-Options "nosniff" always;), ensuring headers persist even on 4xx and 5xx error responses. For Apache, use Header always set directives in mod_headers. After updating configs, reload the server with nginx -t && systemctl reload nginx and re-test public endpoints against automated security header checkers to verify production compliance.

Security headers: HTTPS, CSP, clickjacking, MIME, referrer and browser isolation controls.

Start a free audit →

Questions

Frequently Asked Questions

Can I safely enable HSTS preload immediately?

No. Start with a staging test using Strict-Transport-Security with max-age=86400 (one day) and confirm all subdomains and API endpoints are reachable over valid HTTPS before submitting to hstspreload.org with max-age=31536000.

How do I deploy Content Security Policy without breaking analytics or external scripts?

Deploy Content-Security-Policy-Report-Only first to log violations, identify required external endpoints, and use nonces or SHA-256 hashes for inline scripts before transitioning to full policy enforcement.