Skip to content
RESEARCH INDEX BREACHROAD / INTELLIGENCE NOTE
Web security

HTTP Security Headers: A Practical Guide for Businesses

HSTS, CSP, X-Frame-Options, Permissions-Policy, CORS and cookie flags - which security headers to implement, how to set them correctly and how to verify them.

PUBLIC RESEARCH
AUTHOR
/ Penetration Tester (OSCP, PNPT)
PUBLISHED
4 July 2026
READING TIME
12 min read
TOPIC
Web security
HTTP Security Headers: A Practical Guide for Businesses

HTTP security headers are one of the few security measures that cost a few lines in the server configuration and can block entire classes of attacks: clickjacking, part of XSS, connection downgrade or address leakage via the Referer header. When testing web applications, their absence is one of the most common findings - not because they are difficult, but because no one has dealt with them. This guide sorts out what to implement, how to set it up correctly, and how to check the effect.

Strict-Transport-Security (HSTS)

HSTS tells the browser: “Only connect to this domain over HTTPS, even if the user types http://.” Without this header, the first HTTP request can be intercepted and redirected (SSL stripping attack) before the server even enforces encryption.

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

Three elements matter:

  • max-age - time (in seconds) for which the browser remembers the rule. A reasonable minimum is 6 months (15552000), ultimately a year.
  • includeSubDomains - extends the rule to all subdomains. Without it, an attacker may redirect users through an unprotected host such as login.example.com.
  • preload - allows you to place the domain on the list built into browsers, thanks to which HTTPS is enforced on the first visit. It requires reporting to hstspreload.org and is difficult to discontinue - only enable it when the entire domain and subdomains are definitely running over HTTPS.

A common error that we detect in scans: HSTS present, but with max-age within a few minutes or without includeSubDomains - formally it “is there”, in reality it protects almost nothing.

Content-Security-Policy (CSP)

CSP is the strongest and most difficult to configure header. Defines from what sources the browser can load scripts, styles, images or frames. A well-placed CSP is the last line of defense against cross-site scripting (XSS) - even if an attacker manages to inject the script, the browser will refuse to execute it.

The problem is that the mere presence of a header means nothing if the policy is full of holes:

# Weak CSP: technically present, practically ineffective
Content-Security-Policy: default-src * 'unsafe-inline' 'unsafe-eval'

'unsafe-inline' allows you to execute scripts embedded directly in HTML - exactly what typical XSS does. 'unsafe-eval' allows eval(). Wildcard * allows any source. Such a CSP gives a false sense of security and that is why in our scanner we evaluate not only the presence, but also the quality of the policy.

Practical approach to implementation:

  1. Start with reporting mode (Content-Security-Policy-Report-Only) - the policy does not block anything, but reports violations. You will collect a list of legitimate sources without breaking the website.
  2. Build a policy based on a domain whitelist or, better yet, on nonces or hashes for inline scripts, instead of 'unsafe-inline'.
  3. Set object-src 'none' and base-uri 'self' - these are low-cost restrictions that close known bypass vectors.
  4. Only after the observation period, switch to enforcement mode.

Clickjacking protection: frame-ancestors / X-Frame-Options

Clickjacking involves embedding your website in an invisible frame on the attacker’s website and persuading the user to blindly click. Defense:

Content-Security-Policy: frame-ancestors 'self'
# Or, for older browsers:
X-Frame-Options: DENY

The modern standard is the frame-ancestors directive in CSP. Treat X-Frame-Options as a supplement for older browsers. If you do not want the page embedded anywhere, set DENY. If it may be embedded only on your own origin, use SAMEORIGIN or frame-ancestors 'self'.

X-Content-Type-Options

X-Content-Type-Options: nosniff

One header, one value, zero configuration - and it blocks MIME sniffing, where the browser guesses the content type. Without nosniff, a file uploaded as an image may be interpreted as a script. There is little reason not to set it on every response.

Referrer-Policy

The Referer header (yes, the typo in the original spec) tells the landing page where the user came from - including the full address, which may include session IDs, tokens, or internal paths. Referrer-Policy limits this leakage:

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

This value sends the full address only within your own domain, and only origin (without path and parameters) to third parties. This is a reasonable default choice for most websites.

Permissions-Policy

Formerly Feature-Policy. It allows you to disable access to sensitive browser APIs (camera, microphone, geolocation, payments) that your application does not use:

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

This is a “just in case” restriction: if an attacker injects code, they won’t reach the API, which wasn’t needed anyway. List what you actually use and block the rest.

CORS: Access-Control-Allow-Origin

CORS is sometimes misunderstood - it is not a standalone website defence, but a mechanism for controlled relaxation of the same-origin policy. One of the most dangerous errors is reflecting any supplied Origin:

# Very dangerous when combined with session cookies:
Access-Control-Allow-Origin: <any reflected origin>
Access-Control-Allow-Credentials: true

This configuration allows any website on the Internet to make an authenticated request to your API on behalf of the logged in user and read the response. This is a real data leak, not a theory. Rules:

  • Do not reflect Origin blindly - maintain an allowlist of trusted origins.
  • Use Access-Control-Allow-Credentials: true only with a specific trusted origin, never with *.
  • For public, unauthenticated resources, wildcard * without credentials is fine.

We write more about typical errors in programming interfaces in the article API security according to OWASP.

The headers are not just Set-Cookie the other way round. Session cookies should have three flags:

  • Secure - sent only over HTTPS.
  • HttpOnly - not available from JavaScript, making session stealing more difficult via XSS.
  • SameSite=Lax (or Strict) - Limits cookies from being sent to requests from other sites, which is the primary defense against CSRF.

The absence of any of these flags in a session cookie is a standard finding in an application audit.

Mixed content: HTTPS that is not fully HTTPS

Even on a website served over HTTPS, a single script or image loaded over http:// (so-called mixed content) opens the door to content manipulation and destroys encryption guarantees. Browsers block active mixed content, but it’s worth eliminating it at the source - all resources should go over HTTPS.

What not to do

  • Do not rely solely on headers. CSP will not fix a vulnerable application - it is a mitigation layer, not a replacement for input validation. Headers and OWASP Top 10 are complementary layers.
  • Do not set X-XSS-Protection. This header is deprecated and in some cases introduced vulnerabilities on its own. Modern browsers ignore it - use CSP instead.
  • Do not blindly copy someone else’s CSP policy. The policy is strictly dependent on what your application loads. If copied from the Internet, it will either block half the page or be too liberal.

How to verify your configuration

Manually checking every header on every subdomain is tedious. Our free website security scanner checks a complete set of headers (HSTS, CSP with quality rating, X-Frame-Options, nosniff, Referrer- and Permissions-Policy), CORS configuration, mixed content, cookie flags and certificate expiration - and returns the result with specific recommendations. We explain how to read such a report in the article How to read the security scan result.

However, the passive scanner only shows the hygiene of the configuration. Real vulnerabilities in the application logic, authorization bypasses or error chains are only detected by manual penetration testing - contact us if you want to check the application deeper than at the header level.

Summary: Minimal set

For a typical web application, a reasonable starting point is:

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

…plus Secure; HttpOnly; SameSite=Lax flags on session cookies and restrictive CORS. This is the foundation that closes the most common, cheap-to-fix vulnerabilities - a review of the entire application should take care of the rest.

Frequently asked questions (FAQ)

Can adding security headers break something? Yes - CSP in particular can block legitimate scripts and styles if the policy is too restrictive. Therefore, CSP is implemented in stages, starting with Report-Only mode. The remaining headers (HSTS, nosniff, Referrer-Policy) are usually safe to enable immediately, although before using HSTS with preload you must ensure that the entire domain runs over HTTPS.

Where do you set these headers - in the application or on the server? You can in both places. Most often, they are set at the level of the web server (nginx, Apache), reverse proxy or CDN - thanks to this, they cover the entire application uniformly. Context-dependent headers (e.g. nonce in CSP) are generated by the application.

Do security headers affect SEO? They are not directly a ranking factor, but HTTPS and no mixed content are, and proper configuration builds trust and eliminates browser warnings that discourage users. This is an indirect but real influence.

How often to check header configuration? After each major application or infrastructure change, and periodically thereafter - configuration tends to drift during migrations and new service deployments. A quick free security scan takes seconds and works well as a regular check.

SHARE / COPY