HTTP security headers: what they do, how to check them, how to set them

HSTS, nosniff, Referrer-Policy and CSP: what each header prevents, what the scanner checks, and how to set them correctly.

Typical severity: Medium · Checked by NovaPentest module: HTTP Headers

What it is

HTTP security headers are response headers that tell the browser how to treat your site. They cost nothing at runtime and are set once, at the web server, the reverse proxy or the application.

Strict-Transport-Security (HSTS) tells the browser to use HTTPS for your domain for a set period, even if a user types http:// or follows an old link. After the first visit over HTTPS, the browser rewrites plain HTTP requests to HTTPS before they leave the machine.

X-Content-Type-Options: nosniff tells the browser to trust the Content-Type you send rather than guess it from the file contents. This stops an uploaded text or image file from being interpreted as script or HTML.

Referrer-Policy controls how much of the current URL is sent in the Referer header when a user follows a link or the page loads a third-party resource. Without it, full paths and query strings can reach analytics providers, CDNs and any external site you link to.

Content-Security-Policy (CSP) restricts where scripts, styles, frames and other resources may load from, and is the main browser-side defense that limits the damage of an injected script. It deserves its own careful rollout, which is why it is only touched on here.

Two headers work in the opposite direction: Server and X-Powered-By. They add no protection and only advertise the software, and sometimes the exact version, running behind the site.

None of these headers fixes a bug in your code. They reduce what an attacker can do with one, and they close gaps that exist between the browser and your server.

How it is exploited

Without HSTS, the first request a browser makes to your site is often plain HTTP: a typed domain name, a bookmark saved years ago, a link in an old email. On a shared or hostile network such as public Wi-Fi, whoever controls the network can answer that request instead of your server and keep the user on an unencrypted copy of the site. The user sees a normal-looking page without the padlock, and anything they type, including passwords and session cookies that lack the Secure flag, is readable in transit. HSTS removes that first unencrypted hop for returning visitors.

Without X-Content-Type-Options: nosniff, a browser that receives a file with a vague or wrong content type may decide for itself what it is. If your site accepts uploads and serves them back from your own domain, a file that passed your validation as an image can be treated as HTML or script, running in your origin with access to your users' sessions.

Without a Referrer-Policy, older browsers and some configurations send the full URL of the page to every external destination. URLs often carry more than intended: password reset tokens, invitation links, search terms, internal record IDs. Those end up in third-party logs you do not control.

Without CSP, if an injection bug exists anywhere on the site, the injected script can load code from any domain and send data anywhere.

Server and X-Powered-By headers do not create a weakness by themselves, but an exact version string makes it trivial to match your site against lists of known vulnerable releases, which shortens the work of automated scanning.

How to detect it

NovaPentest's HTTP Headers module sends one GET request to the target URL, follows redirects, and inspects the headers of the final response. It reports a missing Strict-Transport-Security header as High, a missing Content-Security-Policy as Medium, and a missing X-Content-Type-Options or Referrer-Policy as Low. It also reports, as Info, any Server or X-Powered-By header it sees, with the value as evidence. The module runs in the free passive scanner at novapentest.com/scan as well as in full scans.

Be aware of what this check does not do. It mostly tests whether each header is present, not whether its value is sound. The exception is HSTS, where max-age=0 is reported because it switches protection off, and HSTS is only checked on HTTPS responses, where browsers honor it. A CSP that allows everything will still pass this check. It looks at a single page, so headers your application sets on some routes but not others will not be spotted. And the Server header is reported whenever it exists, even if it only says nginx with no version.

To verify by hand, fetch your home page's headers with curl -sI https://example.com and look for the lines you expect. Add -L to follow redirects the way a browser does, and repeat for a few representative paths such as a login page, an API endpoint and a static file, because headers are often added by one layer and missing in another. A correct result includes lines like Strict-Transport-Security: max-age=31536000; includeSubDomains, X-Content-Type-Options: nosniff and Referrer-Policy: strict-origin-when-cross-origin. Check the values, not just the names: a max-age of a few seconds gives almost no protection. Your browser's developer tools show the same information in the Network tab under Response Headers.

How to fix it

1. Decide where headers are set. Pick one layer, usually the reverse proxy or the application, so they are applied to every response, including error pages and static files. Setting the same header in two layers can produce duplicates that browsers handle inconsistently.

2. Add HSTS only after every subdomain is reachable over HTTPS, because includeSubDomains applies to all of them. Start with a short max-age such as 300, confirm nothing breaks, then raise it to 31536000. In nginx: add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; In Apache: Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains". In ASP.NET Core call app.UseHsts() in production; in Express the helmet package sends it by default. Browsers ignore HSTS received over plain HTTP, so it only needs to be sent over HTTPS.

3. Add X-Content-Type-Options: nosniff everywhere and make sure every response carries an accurate Content-Type, especially user uploads. In nginx: add_header X-Content-Type-Options "nosniff" always;

4. Add Referrer-Policy: strict-origin-when-cross-origin as a sensible default, or no-referrer for pages whose URLs carry tokens. In IIS, add both headers under system.webServer/httpProtocol/customHeaders in web.config.

5. Remove or trim disclosure headers. In nginx set server_tokens off; in Apache set ServerTokens Prod and ServerSignature Off; in Express call app.disable('x-powered-by'); in ASP.NET Core on Kestrel set options.AddServerHeader = false.

6. Plan CSP separately. Begin with Content-Security-Policy-Report-Only to see what would be blocked, fix the violations, then enforce.

7. Re-run curl -sI against several paths after each deploy, and add the check to your pipeline so a configuration change cannot silently drop the headers. Note that in nginx, add_header directives inside a location block replace, rather than extend, those defined at server level.

Check your own site

Start with the free passive scan: no account needed, and you get an A to F grade covering headers, TLS, cookies, DNS and other configuration a visitor can observe. It does not send attack payloads, so it will not confirm every class of flaw. The full scan, available with a free account, runs the active checks, including the one described on this page.

Scan your site free