Cybreach Consulting Prendre RDV
Back to articles
Vulnerabilities

HTTP security headers: what your server isn't telling the browser

HTTP security headers are instructions your server sends to the browser to tell it how to behave. They require no application code, no dependencies, no complex redeployment. A single line of Nginx or Apache config is enough. Yet in the vast majority of audits, these headers are missing, incomplete, or misconfigured.

This is not a cosmetic detail. The absence of these headers leaves attack vectors open that the browser could natively block: clickjacking, MIME confusion, HTTP resource injection on HTTPS pages, facilitated session theft. Here are the six headers that really matter, and how to configure them without breaking production.

Strict-Transport-Security (HSTS)

HSTS forces the browser to use HTTPS only for your domain, for a set duration. Without this header, a user who types mysite.com in their address bar first makes an HTTP request, which is then redirected to HTTPS. That initial HTTP round-trip happens in the clear: it can be intercepted by an attacker in a Man-in-the-Middle position on the network.

Recommended configuration

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

max-age=31536000: the browser remembers the rule for 1 year. includeSubDomains: all subdomains inherit the rule. preload: allows listing your domain in browsers' preloaded HSTS list (your domain will be forced to HTTPS even on the very first visit, before the browser has ever received the header). Start with a short max-age (300 seconds) during testing, then increase gradually.

Content-Security-Policy (CSP)

CSP is the defence-in-depth layer against XSS attacks. It tells the browser which sources of scripts, styles, images, and other resources are allowed to load on your page. A script injected via XSS that tries to load code from an external domain will be blocked if that domain isn't in the policy.

Strict CSP for a SPA with no external CDN

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; frame-ancestors 'none';

CSP is the most complex header to deploy because an overly strict policy breaks legitimate resources. The right approach: first deploy in Content-Security-Policy-Report-Only with a reporting endpoint, measure violations for a few days, then switch to blocking mode. 'unsafe-inline' and 'unsafe-eval' in script-src cancel most of the protection, so avoid them.

X-Content-Type-Options

This header prevents the browser from guessing (sniffing) the MIME type of a response. Without it, some browsers ignore the declared Content-Type and try to infer the type from the actual content. An attacker can exploit this: upload a text file containing JavaScript, the server declares it text/plain, but the browser detects it as text/javascript and executes it.

Configuration

X-Content-Type-Options: nosniff

This is the simplest header to deploy. It has only one possible value, no side effects, and has been supported by all modern browsers for years. There is no valid reason not to enable it.

X-Frame-Options and frame-ancestors

Clickjacking involves embedding your page in an invisible <iframe> overlaid on another page, trapping the user into clicking elements of your application without knowing it. The X-Frame-Options header prevents your page from being embedded in an iframe.

Two approaches depending on your needs

# Option 1 : interdire tout embedding (recommandé si vous n'utilisez pas d'iframes)
X-Frame-Options: DENY

# Option 2 : n'autoriser que le même domaine
X-Frame-Options: SAMEORIGIN

# Approche moderne via CSP (remplace X-Frame-Options)
Content-Security-Policy: frame-ancestors 'none';

The CSP frame-ancestors directive is more flexible and advantageously replaces X-Frame-Options in modern browsers. It allows defining a precise list of domains allowed to embed your page. If you use both, frame-ancestors takes precedence in supporting browsers.

Mixed content

Mixed content refers to loading HTTP (unencrypted) resources from an HTTPS page. A script loaded over HTTP on an HTTPS page can be modified by an attacker in a Man-in-the-Middle position: the browser established a secure connection with your server, but the resource itself travels in the clear across the network.

Modern browsers block active mixed content (scripts, iframes) but may still load passive mixed content (images, CSS) depending on configuration. The upgrade-insecure-requests header asks the browser to automatically upgrade all HTTP requests to HTTPS:

Force HTTPS for all resources

Content-Security-Policy: upgrade-insecure-requests;

Permissions-Policy

This header controls access to sensitive browser APIs: camera, microphone, geolocation, accelerometer, payment. It replaces the old Feature-Policy. It is particularly useful for limiting what third-party scripts (ads, analytics) can do.

Disable unused APIs

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

Deployment: Nginx, Apache, Netlify

Nginx

server {
  add_header Strict-Transport-Security "max-age=31536000; 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=()" always;
  add_header Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'" always;
}

Netlify (_headers)

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

Summary

  1. HSTS: enforces HTTPS, protects against downgrade attacks and interception on the first HTTP round-trip
  2. CSP: defence-in-depth against XSS, controls allowed resource sources. Deploy in report-only mode first
  3. X-Content-Type-Options: nosniff: prevents MIME sniffing. No reason not to enable it
  4. X-Frame-Options / frame-ancestors: prevents clickjacking by blocking embedding in iframes
  5. upgrade-insecure-requests: forces HTTPS for all resources, eliminates mixed content
  6. Permissions-Policy: limits access to sensitive browser APIs for third-party code

A question after reading?

Want a security header audit of your application? Send a message.

Or book a 30-minute scoping call directly

Book a call