Every time a server sends a page, it also sends invisible instructions called response headers. A handful of them, known as HTTP security headers, tell the browser to switch on protections it would not use by default: refusing to load the site over plain HTTP, blocking your pages from being framed by other sites, and limiting what scripts can do. They cost nothing, take minutes to add, and close off several common attack techniques.
See what your site sends today
Start by checking your current headers. Our HTTP header checker shows the full response from any URL. From a terminal you can use:
curl -sI https://example.com
The -I flag fetches headers only. Note which of the headers below are missing, then add them one at a time.
The essential HTTP security headers
Strict-Transport-Security (HSTS)
Strict-Transport-Security: max-age=31536000; includeSubDomains
Tells the browser to use HTTPS for your domain for the next year (31,536,000 seconds), even if someone types http:// or clicks an old link. This stops attackers on public Wi-Fi from intercepting that first unencrypted request. Only send it once every page and subdomain works over HTTPS with a valid certificate (an SSL checker confirms this quickly), and start with a short max-age if you are unsure.
Content-Security-Policy (CSP)
Content-Security-Policy: default-src 'self'; frame-ancestors 'self'; object-src 'none'; base-uri 'self'
Lists the sources from which the browser may load scripts, styles, images and other resources. A well-built policy is one of the strongest defences against cross-site scripting (XSS), where an attacker gets their JavaScript to run on your page. CSP is the most powerful header here and also the easiest to break a site with, so roll it out in report-only mode first using Content-Security-Policy-Report-Only.
X-Content-Type-Options
X-Content-Type-Options: nosniff
Stops browsers from "sniffing" a file and deciding it is a different type from what the server declared. Without it, a file uploaded as an image could, in some situations, be treated as a script. There is only one valid value, and it is safe to add everywhere.
Clickjacking protection: frame-ancestors or X-Frame-Options
X-Frame-Options: SAMEORIGIN
Clickjacking is when another site loads yours in an invisible frame and tricks users into clicking buttons they cannot see. The modern control is the CSP directive frame-ancestors; X-Frame-Options is the older header that does the same job. Sending both is harmless and covers older browsers. Use DENY if your pages never need to be framed, or SAMEORIGIN if your own site frames them.
Referrer-Policy
Referrer-Policy: strict-origin-when-cross-origin
When a visitor clicks a link to another site, the browser can send the full URL they came from. That URL may contain search terms, account IDs or reset tokens. This value sends the full URL within your own site, only the domain to other HTTPS sites, and nothing when moving from HTTPS to HTTP. It is a sensible default for most businesses.
Permissions-Policy
Permissions-Policy: camera=(), microphone=(), geolocation=()
Switches off powerful browser features your site does not use. Empty parentheses mean "no one, not even this site". If a malicious script is ever injected, it cannot quietly request the camera or location. Adjust the list to match what your site genuinely needs.
Headers to remove or stop using
- X-XSS-Protection: controlled an old browser XSS filter that modern browsers have removed, and the filter itself could introduce problems. Omit it or set it to
0; rely on CSP instead. - Public-Key-Pins (HPKP) and Expect-CT: both are obsolete and no longer supported by major browsers.
- Server and X-Powered-By version details: lines like
Server: Apache/2.4.41 (Ubuntu)orX-Powered-By: PHP/8.1.2help attackers target known vulnerabilities. Hiding them is not a substitute for patching, but there is no reason to advertise. In Nginx useserver_tokens off;, in ApacheServerTokens ProdandServerSignature Off, and in PHP setexpose_php = Off.
Adding the headers
Nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" 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;
The always parameter makes Nginx send the header on error pages too. Watch out for one Nginx quirk: if a location block has any add_header of its own, it does not inherit those from the parent server block.
Apache
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
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=()"
This requires mod_headers (sudo a2enmod headers on Debian and Ubuntu).
If your site runs behind a CDN, many CDNs let you add response headers in their dashboard, which is handy when you cannot edit the origin server.
Rolling out safely
- Add the low-risk headers first:
X-Content-Type-Options,Referrer-Policyand the framing header. - Add HSTS with a short
max-agesuch as 300, confirm nothing breaks, then increase it. - Build CSP in report-only mode, fix what it reports, then enforce it.
- Re-test after each change and after every major site update.
For full reference documentation on each header, MDN's HTTP headers reference is reliable and up to date.
Key takeaways
- Security headers turn on browser protections against downgrade, clickjacking, MIME sniffing and XSS.
- The core set is HSTS, CSP, X-Content-Type-Options, frame protection, Referrer-Policy and Permissions-Policy.
- Drop X-XSS-Protection, HPKP and Expect-CT, and hide server version strings.
- Introduce HSTS and CSP gradually; they are powerful enough to break things if rushed.