Why headers
Most web attacks on users, as opposed to on servers, work by getting a browser to do something with your site that you did not intend: load your page in a hidden frame, run a script injected into a comment, downgrade a connection to plain HTTP, send a session cookie to a third party. Browsers will refuse to do all of those if the site tells them to, and the way you tell them is a response header. They cost nothing to add, they are enforced by the client, and they close whole classes of problem at once.
To see which ones a site already sends, run it through the HTTP header checker; the security scorecard checks each of the headers below and explains what it found.
The headers
Strict-Transport-Security (HSTS)
Tells the browser to use HTTPS for this host for the next N seconds without ever trying HTTP first, and to refuse to continue past certificate errors. Prevents SSL stripping on the first request and stops users clicking through warnings.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Start with a short max-age (say 300) while you confirm every subdomain works over HTTPS, then raise it to a year. includeSubDomains is important (cookies set on the parent are otherwise exposed via an HTTP subdomain) but is exactly what breaks when you forget an internal host. preload gets the domain into the browsers' built-in list so even the very first visit is protected; only add it once you are certain, because getting removed takes months. Only send it on HTTPS responses.
Content-Security-Policy (CSP)
Whitelists where scripts, styles, images, fonts, frames and connections may come from. The main defence against cross-site scripting: an injected <script> from a domain not on the list, or inline in the page, simply does not run.
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'
This is the one that takes work. Inline scripts and onclick attributes have to move into files, or be allowed with nonces or hashes. Third-party widgets need their origins listed. Deploy it as Content-Security-Policy-Report-Only first with a report-to endpoint, watch what would have been blocked, fix, then enforce. 'unsafe-inline' on script-src makes the whole thing decorative; avoid it. On style-src it is a smaller compromise that some third-party widgets force on you.
X-Frame-Options and frame-ancestors
Stops other sites embedding your pages in an iframe, which is how clickjacking works (an invisible frame of your "delete account" button under their "click to win" button).
X-Frame-Options: DENY Content-Security-Policy: frame-ancestors 'none'
frame-ancestors is the modern replacement and supports a list of allowed origins; X-Frame-Options is for old browsers. Send both. Use SAMEORIGIN / 'self' if you frame your own pages.
X-Content-Type-Options
X-Content-Type-Options: nosniff
Stops the browser guessing a content type that differs from the Content-Type header. Without it, a user-uploaded "image" that is really HTML with a script can be executed if served from your origin. One value, no downside, always send it.
Referrer-Policy
Referrer-Policy: strict-origin-when-cross-origin
Controls how much of the current URL is sent in the Referer header when a user follows a link or a resource loads. The default in modern browsers is already strict-origin-when-cross-origin (full URL to your own site, origin only to others, nothing on HTTPS to HTTP), but setting it explicitly protects older browsers and documents the intent. Use no-referrer if URLs carry tokens or personal data.
Permissions-Policy
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Switches off browser features your site does not use, for your own pages and for anything you embed. If a third-party script is compromised it cannot turn on the camera. List the features you do not need with empty allow-lists.
Cross-Origin-Opener-Policy and friends
Cross-Origin-Opener-Policy: same-origin Cross-Origin-Resource-Policy: same-origin
COOP puts your window in its own browsing context group so a page you opened (or that opened you) cannot reach window.opener. CORP stops other origins loading your resources as subresources. Together with Cross-Origin-Embedder-Policy they also unlock SharedArrayBuffer. Low risk to add for a normal site; test if you rely on popups or cross-site embedding.
Cookie flags
Not a header of their own, but attributes on every Set-Cookie:
Set-Cookie: session=...; Secure; HttpOnly; SameSite=Lax; Path=/
Secure: never sent over HTTP. HttpOnly: not readable by JavaScript, so XSS cannot steal it. SameSite=Lax: not sent on cross-site POSTs, which kills most CSRF; Strict also blocks cross-site top-level navigations, which breaks arriving from an email link while logged in. Prefix the name with __Host- to lock it to this host and path.
Headers to remove
Server: nginx/1.18.0, X-Powered-By: PHP/7.4.3, X-AspNet-Version. They tell an attacker exactly which advisories to read. Strip the version or the whole header. X-XSS-Protection is obsolete and was itself a source of bugs; remove it rather than set it.
A sensible starting set
Strict-Transport-Security: max-age=31536000; includeSubDomains Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self' X-Frame-Options: DENY X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-origin Permissions-Policy: camera=(), microphone=(), geolocation=() Cross-Origin-Opener-Policy: same-origin
Order of effort: nosniff, X-Frame-Options, Referrer-Policy and Permissions-Policy are one line each and cannot break anything; add them today. HSTS needs a check that every subdomain is on HTTPS; add it this week with a short max-age. CSP is a project; start in report-only mode. Set them at the edge (reverse proxy or CDN) so every application behind it inherits them, and check the result with something that shows you the actual headers rather than a marketing grade.