Apache uses the mod_headers module to add, modify, and remove HTTP response headers. This guide shows how to configure all seven essential security headers — HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and X-XSS-Protection — using both the VirtualHost configuration and .htaccess files.
Prerequisites
- Apache 2.4+ installed and serving your site over HTTPS.
- The
mod_headersmodule enabled (see below). - A valid SSL certificate (required before enabling HSTS).
- Access to the server configuration or
.htaccessfiles.
Step 1: Enable mod_headers
On Debian/Ubuntu systems, enable the module and restart Apache:
sudo a2enmod headers
sudo systemctl restart apache2On RHEL/CentOS systems, mod_headers is typically compiled in or loaded via the main configuration. Verify it is active:
httpd -M | grep headersStep 2: Add Headers to Your VirtualHost
The recommended approach is to add headers directly to the <VirtualHost> block in your site configuration. Open the relevant file (usually in /etc/apache2/sites-available/ or /etc/httpd/conf.d/):
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
DocumentRoot /var/www/example.com
# ── Security Headers ──────────────────────────────────────────────────
<IfModule mod_headers.c>
# Force HTTPS for 1 year; include subdomains; opt into preload lists
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Restrict resource loading; adjust script-src for any CDNs you use
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
# Prevent clickjacking
Header always set X-Frame-Options "DENY"
# Prevent MIME-type sniffing
Header always set X-Content-Type-Options "nosniff"
# Send origin only on same-site requests; nothing on cross-site downgrades
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Block sensitive browser features from the page and iframes
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), fullscreen=(self), payment=()"
# Disable the defunct IE/Chrome XSS Auditor
Header always set X-XSS-Protection "0"
</IfModule>
# ── End Security Headers ──────────────────────────────────────────────
<Directory /var/www/example.com>
Options -Indexes
AllowOverride None
Require all granted
</Directory>
</VirtualHost>
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
# Redirect all HTTP to HTTPS
RewriteEngine On
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</VirtualHost>Alternative: Using .htaccess
If you do not have access to the VirtualHost configuration (e.g., on shared hosting), you can add headers in a .htaccess file in your document root. This requiresAllowOverride FileInfo or AllowOverride All to be set for the directory.
# .htaccess
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), fullscreen=(self), payment=()"
Header always set X-XSS-Protection "0"
</IfModule>.htaccess files on every request unless AllowOverride None is set. For production sites, prefer setting headers in the VirtualHost configuration and use AllowOverride None to disable .htaccess parsing entirely.Header-by-Header Explanation
Strict-Transport-Security (HSTS)
Instructs browsers to always use HTTPS when connecting to your domain. The max-age is in seconds (31536000 = 1 year). Add includeSubDomains only after confirming all subdomains serve valid HTTPS. Add preload only when committed to HTTPS permanently — inclusion in browser preload lists is difficult to reverse.
Content-Security-Policy (CSP)
Defines which origins can supply scripts, styles, images, and other resources. The example policy is conservative. Expand script-src to add any CDNs or analytics services your site uses. Test with Content-Security-Policy-Report-Only first.
Content-Security-Policy with Content-Security-Policy-Report-Only during development. Violations are logged to the browser console but resources are not blocked, letting you tune the policy safely.X-Frame-Options
Prevents your pages from being loaded inside iframes on other domains, blocking clickjacking attacks. DENY blocks all framing. Use SAMEORIGIN if your application legitimately embeds its own pages in iframes.
X-Content-Type-Options
Forces the browser to respect the declared Content-Type and disables MIME sniffing. Prevents an attacker from uploading a file (e.g., an image) that is actually a script and having the browser execute it.
Referrer-Policy
Controls how much URL information is included in the Referer header on navigation. strict-origin-when-cross-origin protects sensitive URL paths from leaking to third-party sites while still providing referrer data for same-site analytics.
Permissions-Policy
Limits which browser APIs (camera, microphone, geolocation, etc.) can be accessed by your page and any embedded iframes. Blocking features you do not use prevents a compromised third-party script or embed from silently accessing them.
X-XSS-Protection
Set to 0 to disable the deprecated XSS Auditor in older browsers. Chrome removed the auditor in 2019; Safari removed it in 2022. Sending 0 prevents any lingering IE11 instances from running the legacy (and potentially exploitable) filter.
Step 3: Test and Reload
# Test configuration for syntax errors
sudo apachectl configtest
# Reload Apache to apply changes
sudo systemctl reload apache2
# Verify headers with curl
curl -I https://example.comOr use the ShowDNS Security Headers Scanner to check all headers at once and get a graded report.
Frequently Asked Questions
Why wrap headers in IfModule mod_headers.c?
The <IfModule> wrapper prevents Apache from failing to start if mod_headers is not loaded. On well-maintained servers where the module is always present, it is optional but harmless.
What does Header always mean?
The always condition tells Apache to set the header regardless of the HTTP response status — including error responses like 404 or 500. Without always, headers are only added to successful (2xx) responses.
Can I set different headers for different directories?
Yes. Wrap header directives in a <Directory>, <Location>, or <Files> block to apply them selectively. More specific contexts override less specific ones for the same header name.