← Back to journal

Check your website's security in one minute: the HTTP headers that matter

Your site's HTTP headers reveal in seconds whether the basic protections are in place. An explanation of each header, an online checking tool, and ready-to-copy configurations for Nginx, Apache and Node.js.

Every time a page loads, your server sends the browser a dozen invisible lines: the HTTP headers. Some tell the browser what it may do with the page. Others inadvertently reveal which technology runs behind it, and in which version. Reading those lines takes a few seconds and gives a first picture of how carefully the configuration was done.

This article explains what each header protects, then offers a tool that queries your site and tells you what is missing. It sits at the bottom of this page, and on its own page.

What a header can prevent, and what it cannot

Take a common case: a poorly filtered comment field lets a piece of JavaScript through, or a library loaded from a CDN is swapped for a booby-trapped version. Without protection, the browser runs that code as if it came from you. With a properly written content security policy, it refuses any script that does not come from a source you listed. The attacker then has to compromise a specific domain, rather than slip code in anywhere.

That is the principle behind every security header. They do not fix a flaw in your code. They limit what an attacker can do with it.

They also have a limit, and the Shopify stores hijacked in October 2026 illustrate it well. The malicious code was served by the vendor of a legitimate app, so from a domain the merchant would have allowed anyway. A security policy would have blocked nothing. When the danger comes from a trusted extension, the only defence is to keep the number of extensions down and watch them.

The headers under review

HTTPS and Strict-Transport-Security

HTTPS encrypts the exchange. But a visitor who types your address without the prefix lands on HTTP first, and that is the moment an attack on a public network can intercept them. The HSTS header asks the browser to stop trying HTTP for your domain, for the stated duration. It only protects from the second visit onwards: the browser must have seen the header once to remember it. To cover the very first visit too, the domain has to be added to the browsers’ preload list, with the preload directive. Six months is the useful minimum, one year is the common value.

Content-Security-Policy

This is the most powerful header, and the trickiest to write. It lists the allowed sources for scripts, styles, images, fonts and connections. Anything not listed is blocked. A policy that allows * for scripts brings nothing. A policy that is too strict breaks the site. The right method: start in report-only mode, read the violations browsers send back, then switch to enforcing mode.

X-Frame-Options and frame-ancestors

The X-Frame-Options header and the frame-ancestors directive of the CSP prevent a page of your site from being shown in an invisible frame on a third-party site. The classic trap: an attractive button layered over your “Confirm” button, to make the visitor click without knowing. The directive is gradually replacing the header, but the two coexist without conflict.

Referrer-Policy

When a visitor clicks an external link from your site, the browser passes along the address of the originating page. With a permissive policy such as unsafe-url, a third-party site sees the full address, parameters included. The value strict-origin-when-cross-origin only passes the origin, that is the scheme, domain and port. It is also the default in modern browsers, so a missing header does not expose your visitors. Adding it makes the choice explicit and covers older browsers.

Permissions-Policy

This header disables access your site does not use: camera, microphone, geolocation. It is optional, but it closes a door an injected script could open.

A cookie marked Secure only travels over HTTPS. A cookie marked HttpOnly is invisible to the page’s JavaScript, and therefore to an injected script. The SameSite attribute, set to Lax or Strict, stops it being sent from a third-party site, which cuts off forged requests. A session cookie without those three attributes is the first target of an injection attack.

Exposed versions

The Server and X-Powered-By headers sometimes announce the exact version of the server or the language: nginx/1.24.0, PHP/8.3.20. That information is useless to your visitors. To an attacker, it says directly which flaws to try. Since the patches of October 21, 2026, a site announcing a PHP version below 8.2.35, 8.3.36, 8.4.27 or 8.5.12 publicly signals that it is vulnerable to php2shell, a flaw exploited in the wild. The same goes for Node.js with Node2Shell: the fixed versions are 22.23.4, 24.22.1 and 26.12.1. The tool below compares the announced version with those thresholds.

How to fix it

Headers are configured in a single place, depending on your server. Each block below is directly usable; adapt the CSP to the external resources your site actually loads.

Nginx, inside the server block. Beware of the most common trap: as soon as a location block contains its own add_header directive, it no longer inherits those of the server block. In that case, repeat the security headers in that block, or group them in a file included at both levels.

# Forces HTTPS for one year, subdomains included
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Scripts and styles limited to the domain itself; adapt to your external services
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'self'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Do not announce the Nginx version
server_tokens off;

Apache, in the .htaccess file or the site configuration, with the headers module enabled:

Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'self'"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
# Removes the X-Powered-By header added by PHP
Header unset X-Powered-By

To hide the Apache version, the ServerTokens Prod directive goes in the main server configuration (httpd.conf or apache2.conf), never in a .htaccess file, where it causes a 500 error.

PHP, to hide the version whatever the server, in php.ini:

; Removes the X-Powered-By: PHP/x.y.z header
expose_php = Off

Node.js with Express, the helmet library sets the whole lot in a single call. It does not handle Permissions-Policy, which you add by hand to get the same coverage as the Nginx and Apache configurations above:

import helmet from 'helmet';

// Sets HSTS, CSP, nosniff, frame-ancestors, Referrer-Policy and removes X-Powered-By
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", 'data:', 'https:'],
    },
  },
}));

// Permissions-Policy is not covered by helmet
app.use((request, response, next) => {
  response.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
  next();
});

Shopify: you do not control the headers, Shopify manages them for you. Your lever is elsewhere: the number of installed apps and a regular test of your checkout in a private window.

What the tool does not see

The check below reads only the public headers of your home page, once. It does not test your code, your forms, your extensions or your database. A site can get every point green and remain vulnerable through an outdated WordPress plugin. Conversely, a site with several orange points is not necessarily hacked: it simply lacks safeguards.

Take the result for what it is: a first diagnosis. What remains is to check the versions your server does not announce, which the tool therefore cannot read, and, for a site that takes payments or stores customer data, to have a full audit carried out. If the result worries you, or if you do not know who handles these settings on your site, write to me.

Tool

Is your website properly configured?

Enter your website address. The server requests it once, reads its HTTP headers and tells you what is missing. Nothing is stored.