HTTP Security Headers: A Copy-Paste Guide for Nginx and Apache

The HTTP security headers every site should send, what each one does, and ready-to-paste configs for Nginx, Apache and Cloudflare Transform Rules.

By the MakeItFlow team 8 min read
Share LinkedIn X Facebook WhatsApp
HTTP Security Headers: A Copy-Paste Guide for Nginx and Apache

Security headers are a handful of lines in your server config that tell the browser how to treat your pages: only load over HTTPS, don't let other sites frame them, don't guess file types, don't leak full URLs to third parties. They cost nothing to send, and when one is missing it shows up straight away in any audit. This guide covers which headers matter in 2026, which ones to drop, and gives you configs you can paste into Nginx, Apache or Cloudflare.

Key takeaways

  • Send HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy and a starter Content Security Policy.
  • Remove X-Powered-By, X-XSS-Protection, Expect-CT and Public-Key-Pins, as they leak details or are obsolete.
  • In Nginx, add the always parameter so 404 and 500 error pages also carry your security headers.
  • Roll out a strict Content Security Policy in report-only mode first, because it is the easiest header to break a site with.
Watch the explainer
AI Automation for Law Firms, Explained Step by Step · 3:08 Subscribe

The short list

Here is the baseline set, based on the OWASP HTTP Headers Cheat Sheet and MDN:

Header Recommended value What it protects against
Strict-Transport-Security max-age=31536000; includeSubDomains Downgrade to HTTP, cookie theft on public Wi-Fi
Content-Security-Policy Site-specific (start with frame-ancestors, object-src, base-uri) Cross-site scripting, injected scripts, framing
X-Content-Type-Options nosniff MIME-type sniffing turning uploads into scripts
X-Frame-Options DENY or SAMEORIGIN Clickjacking in older browsers
Referrer-Policy strict-origin-when-cross-origin Leaking full URLs (tokens, search terms) to other sites
Permissions-Policy camera=(), microphone=(), geolocation=() Third-party scripts using powerful browser features
Cross-Origin-Opener-Policy same-origin Cross-window attacks via window.opener

And the headers you should remove or stop sending:

  • X-Powered-By and detailed Server values (they advertise your stack and version).
  • X-XSS-Protection. OWASP recommends not setting it, or setting it to 0. The old browser XSS auditors it controlled have been removed, and the header itself could introduce problems. A Content Security Policy is the replacement.
  • Expect-CT and Public-Key-Pins. Both are obsolete and unsupported by modern browsers.

What each header does

Strict-Transport-Security (HSTS)

HSTS tells the browser to use HTTPS for your domain for max-age seconds, and to refuse click-through on certificate errors. Browsers ignore it when it arrives over plain HTTP, so it only takes effect after one successful HTTPS visit. Start with a short max-age (say, 300 seconds) while you confirm every subdomain works over HTTPS, then raise it to a year. The preload directive is a bigger commitment. Our HSTS guide covers it in detail.

Content-Security-Policy (CSP)

CSP is the most powerful header on the list and the easiest to break a site with. It controls where scripts, styles, images, frames and form submissions can come from. Don't paste a strict policy straight into production. Roll it out in report-only mode first. Our Content Security Policy guide walks through that process. A safe starter policy that rarely breaks anything:

Content-Security-Policy: frame-ancestors 'self'; object-src 'none'; base-uri 'self'; upgrade-insecure-requests

This blocks other sites from framing you, disables plugins like <object> and <embed>, stops injected <base> tags from rewriting your relative URLs, and upgrades stray http:// resource requests.

X-Content-Type-Options

nosniff stops the browser from second-guessing the Content-Type you send. Without it, a file uploaded as an "image" that actually contains JavaScript could be run as a script. Make sure your server sends correct content types (and charset=UTF-8 on HTML) before you rely on it.

X-Frame-Options

This is the legacy anti-framing header. CSP frame-ancestors supersedes it, and when both are present, browsers that support CSP ignore X-Frame-Options. It's still worth sending a matching value for older clients. See Clickjacking protection for the differences.

Referrer-Policy

When a visitor clicks a link off your site, the browser sends a Referer header. strict-origin-when-cross-origin sends the full URL to your own pages, only the origin (https://example.com/) to other HTTPS sites, and nothing when going from HTTPS to HTTP. Modern browsers already default to this. Setting it explicitly locks in the behavior and stops audits from flagging it as missing.

Permissions-Policy

This header switches off browser features your site doesn't use, so a compromised third-party script can't use them either. The empty list () means "nobody, not even this site":

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()

If you embed a map that needs location, allow just your own origin: geolocation=(self).

Cross-Origin-Opener-Policy

same-origin puts your page in its own browsing context group, so a cross-origin window that opened yours (or that you opened) can't keep a reference to it. Test it if you use OAuth pop-ups or payment pop-ups that talk back to your page through window.opener. Those may need same-origin-allow-popups.

Nginx configuration

Put these in your server block for the HTTPS site:

server {
    listen 443 ssl;
    http2 on;
    server_name example.com;

    # Hide version number in the Server header
    server_tokens off;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Content-Security-Policy "frame-ancestors 'self'; object-src 'none'; base-uri 'self'; upgrade-insecure-requests" 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;
    add_header Cross-Origin-Opener-Policy "same-origin" always;

    # If PHP or an upstream app adds X-Powered-By, strip it
    fastcgi_hide_header X-Powered-By;
    proxy_hide_header X-Powered-By;
}

Two Nginx gotchas catch almost everyone:

  1. The always parameter. Without it, Nginx only adds the header to 200, 201, 204, 206, 301, 302, 303, 304, 307 and 308 responses. Your 404 and 500 pages go out without protection.
  2. Inheritance. add_header directives are inherited from the enclosing level only if the current level defines no add_header of its own. If a location block adds a single Cache-Control header with add_header, every security header from the server block disappears for that location. The fix is to put the headers in a snippet file and include it in each location that uses add_header.
# /etc/nginx/snippets/security-headers.conf holds the add_header lines above
location /assets/ {
    add_header Cache-Control "public, max-age=31536000, immutable";
    include snippets/security-headers.conf;
}

On Nginx 1.29.3 or newer you can use add_header_inherit merge; instead, which appends the parent level's headers to the ones a location defines. Most distribution packages still ship older versions, so check nginx -v before relying on it.

Test and reload with nginx -t && systemctl reload nginx.

Apache configuration

Enable mod_headers (a2enmod headers on Debian/Ubuntu), then add this to your HTTPS virtual host or .htaccess:

<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
    Header always set Content-Security-Policy "frame-ancestors 'self'; object-src 'none'; base-uri 'self'; upgrade-insecure-requests"
    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=()"
    Header always set Cross-Origin-Opener-Policy "same-origin"
    Header always unset X-Powered-By
    Header unset X-Powered-By
</IfModule>

In the main server config (not .htaccess), reduce the Server header:

ServerTokens Prod
ServerSignature Off

Header always set covers error responses too. The duplicate unset lines are deliberate: Apache keeps two header tables, and a header set by PHP can land in either one. For PHP itself, also set expose_php = Off in php.ini.

Cloudflare configuration

If your site sits behind Cloudflare, you can add headers at the edge without touching the origin:

  1. HSTS and nosniff: In the dashboard, go to SSL/TLS > Edge Certificates > HTTP Strict Transport Security (HSTS). Enable it, choose a max age, and turn on the No-Sniff header option, which sends X-Content-Type-Options: nosniff.
  2. Everything else: Go to Rules and create a Response Header Transform Rule. Set the filter to "All incoming requests" (or your hostname), then add one Set static operation per header: Content-Security-Policy, X-Frame-Options, Referrer-Policy, Permissions-Policy, Cross-Origin-Opener-Policy.
  3. Removing headers: In the same rule type, use the Remove operation for X-Powered-By. Cloudflare doesn't let you change its own Server header.

"Set" overwrites any value your origin sends, while "Add" appends a second copy. For security headers you almost always want "Set". Two CSP headers are both enforced, which can block more than you expect.

Verify your headers

From a terminal:

curl -sI https://example.com/ | grep -iE "strict-transport|content-security|x-content-type|x-frame|referrer-policy|permissions-policy|cross-origin"
curl -sI https://example.com/this-page-does-not-exist | grep -i strict-transport

The second command checks that your 404 page carries the headers too. Also check a few deep URLs and your static assets, especially if you use the Nginx location blocks described above.

Frequently asked questions

Do security headers improve SEO?

Not directly. Google has not listed security headers as a ranking factor, apart from HTTPS itself. They do protect your visitors and your reputation, and a hacked site loaded with spam will definitely hurt your search visibility.

Should I set headers in the application or the web server?

Static headers like HSTS, nosniff and Referrer-Policy belong at the web server or CDN, where they cover every response. A CSP with nonces has to come from the application, because the nonce changes on every request. Pick one place for each header so you don't send duplicates.

Is X-XSS-Protection still needed?

No. Modern browsers have removed the XSS filter it controlled, and OWASP recommends leaving it out or setting it to 0. Put the effort into a Content Security Policy instead.

Why does my scan still show a header as missing after I added it?

Usual causes are a cached response from a CDN, the Nginx inheritance problem described above, or the header only being added on one virtual host (for example www but not the apex domain). Purge the CDN cache and test each hostname with curl -I.

What to do next

Add the baseline headers above, reload your server, and confirm them with curl on the homepage, a 404 page and a static file. Then run a free audit to see which headers are still missing, along with your TLS certificate and exposed-file checks, in one report. Once the basics are in place, the next step is a proper Content Security Policy.

Sources

#security headers #nginx #apache #cloudflare #hsts

Free checklist (PDF)

Free law firms website checklist

30 practical checks for law firms: website speed and mobile, Google Business Profile and reviews, email security, booking and enquiry handling, and AI search visibility. Each one says why it matters, and many link to a free tool that checks it.

We email you the PDF and nothing else unless you tick the box. See the privacy policy.

Written by the MakeItFlow team

We build the website audit these guides are based on. Every fact is checked against the primary sources listed above . How we research and check

Share LinkedIn X Facebook WhatsApp

Check your own site in 30 seconds

Run a free audit covering speed, SEO, DNS, email authentication and security headers — and get a prioritised fix list.

Weekly newsletter

One practical website fix, every week

New guides on speed, SEO, email deliverability and security — straight to your inbox. No spam, unsubscribe in one click.

Keep reading