Set Apache security headers correctly
conf-available/security-headers.confHeader always set X-Content-Type-Options "nosniff"Header always set Referrer-Policy "strict-origin-when-cross-origin"Header always set Content-Security-Policy "frame-ancestors 'self'"Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"sudo a2enmod headerssudo a2enconf security-headerssudo apachectl -t && sudo systemctl reload apache2Read the HSTS warning in the safety section before deploying that last line.
always is not optional
Section titled “always is not optional”Without always, Header set writes to Apache’s onsuccess table, which
applies only to successful responses. Your 404s and 500s ship no security
headers at all.
Header set X-Content-Type-Options "nosniff" # not on error pagesHeader always set X-Content-Type-Options "nosniff" # on everythingError pages are where reflected content and stack traces show up, so they are
precisely where nosniff and a frame policy matter. Use always on every
security header, every time. There is no downside.
Apache does not have nginx’s inheritance trap
Section titled “Apache does not have nginx’s inheritance trap”Worth stating plainly, because it’s the first thing anyone arriving from nginx will worry about.
In nginx, a single add_header in a location block silently discards every
inherited header. Apache’s mod_headers does not work that way: directives from
the server config, the vhost, and <Directory> sections accumulate. Adding a
header in a subdirectory does not delete the ones set above it.
So the nginx failure mode — one path quietly serving no security headers — does not happen here. That’s a genuine point in Apache’s favour and one less thing to audit.
The trap Apache has instead is Header unset and Header set on the same
name: later directives can replace earlier values, and the processing order
(server → vhost → directory → .htaccess) means a .htaccess can override your
carefully set header — which is one more reason for
AllowOverride None.
Do not set X-XSS-Protection
Section titled “Do not set X-XSS-Protection”Every Apache hardening guide still recommends this:
Header always set X-XSS-Protection "1; mode=block" # don'tIt is deprecated, and not harmlessly. The header enabled a browser XSS filter that researchers showed could be turned against the page it was protecting: a crafted URL could make the filter misidentify a legitimate script as an attack and selectively disable it — letting an attacker switch off a page’s own security scripts. MDN states it “can create XSS vulnerabilities in otherwise safe websites”.
Browsers removed the filter. The header does nothing current and it is a documented footgun. Delete the line; a real Content-Security-Policy is the replacement.
X-Frame-Options is superseded
Section titled “X-Frame-Options is superseded”frame-ancestors in CSP is the standard and is strictly more capable — it takes a
source list rather than one origin, and covers <frame>, <iframe>, <embed>
and <object>.
Keeping X-Frame-Options: SAMEORIGIN alongside is harmless for old browsers. Just
don’t set it instead, and don’t let the two disagree — frame-ancestors wins
where it’s supported, which will confuse whoever debugs it later.
Related
Section titled “Related”- Configure Apache TLS — HSTS assumes this works.
- Disable .htaccess — stops a directory overriding your headers.