HTTP response headers are instructions the server sends to the browser along with the page. Some are for caching, others for the content type, and a specific group is there to limit what the browser may do with that page. Configuring them takes a few lines and closes off entire categories of problems. This applies to any site, static ones included.
Strict-Transport-Security (HSTS)
Even if your site redirects from HTTP to HTTPS, the first request from a user who types the address without a prefix may go out unencrypted. HSTS tells the browser: for this domain always use HTTPS, for a certain period, without even trying HTTP.
Strict-Transport-Security: max-age=31536000; includeSubDomains
max-ageis the duration in seconds (one year here).includeSubDomainsextends the rule to all subdomains.
Before enabling includeSubDomains, check that every subdomain works over HTTPS: an old HTTP-only internal subdomain would become unreachable. A cautious strategy is to start with a short max-age, check that everything works and then increase it. There is also the preload directive, which allows the domain to be added to a list built into browsers; it is a choice that is hard to undo, so make it only when you are sure.
X-Content-Type-Options
In the past, browsers tried to "guess" the type of a file by looking at its content, ignoring the type declared by the server. This behaviour can turn a file uploaded as text into an executed script.
X-Content-Type-Options: nosniff
With nosniff the browser trusts only the declared Content-Type. The header has no downsides, provided the server sends correct types for CSS and JavaScript.
Clickjacking protection
Clickjacking means loading your site in an invisible iframe on another page and tricking the user into clicking buttons they cannot see. The modern defence is the frame-ancestors directive of the Content Security Policy:
Content-Security-Policy: frame-ancestors 'self'
With 'self' the page can be framed only by your own site; with 'none', by nobody. The older X-Frame-Options header (with the values DENY or SAMEORIGIN) does the same job and is still useful as reinforcement for older browsers.
Referrer-Policy
When a user clicks a link to another site, the browser may send the address of the originating page in the Referer header. If your URLs contain sensitive information, such as identifiers or search parameters, it ends up with third parties.
Referrer-Policy: strict-origin-when-cross-origin
This value sends the full URL for internal links, only the domain to other sites, and nothing when moving from HTTPS to HTTP. It is also the default behaviour of many modern browsers, but declaring it makes the choice explicit and consistent. For particularly sensitive pages you can use no-referrer.
Permissions-Policy
This header lets you disable browser features the site does not use: camera, microphone, geolocation, payments and more. If a third-party script ever tried to use them, the browser would refuse.
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Empty parentheses mean "no origin allowed". If you need a feature, you can restrict it to your own domain with geolocation=(self).
Content-Security-Policy
This is the most powerful and the most delicate header: it defines which origins the page may load scripts, styles, images and other resources from, and it is a key defence against cross-site scripting. It deserves a separate discussion; here it is enough to know that it should be introduced gradually, starting in report-only mode.
Headers to remove
Security is also about what you do not send. Headers such as Server with the exact version number, or X-Powered-By with the backend language and version, are of no use to users and give information to anyone looking for vulnerable software. Many servers and frameworks let you remove or trim them with a single setting.
A complete example
On many static hosts, headers are declared in a configuration file. A reasonable starting configuration:
/*
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Security-Policy: frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN
With nginx the equivalent is written with the add_header directive, remembering the always parameter so it also applies to error responses:
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
How to check
- Open the developer tools, Network tab, select the main document and look at the response headers.
- From a terminal,
curl -I https://yoursite.comshows only the headers. - There are online services that analyse a site's headers and flag missing ones: useful for a quick check, but interpret the results with judgement.
Check again after every change of host, CDN or configuration: it is easy to lose headers during a migration.
In short
- HSTS enforces HTTPS; enable
includeSubDomainsandpreloadonly after checking. X-Content-Type-Options: nosniffstops the browser from reinterpreting file types.frame-ancestors(andX-Frame-Options) protects against clickjacking.Referrer-PolicyandPermissions-Policylimit the data sent and the features available.- Remove headers that reveal software versions and check again after every migration.