SecHead
Scan a siteContact Us
Platform Guide7 min read

Security Headers for Apache: Complete .htaccess and httpd.conf Guide

Step-by-step Apache security headers configuration for .htaccess and httpd.conf. Covers CSP, HSTS, X-Frame-Options, and more - with copy-paste snippets for Apache 2.4.

SL
Seven Labs · 1 July 2026
1,418 words

Security Headers for Apache: Complete .htaccess and httpd.conf Guide

Apache HTTP Server is still the most widely deployed web server in the world, and yet security header configuration on Apache is frequently incomplete. The default Apache installation sends none of the headers that modern browsers rely on for protection against XSS, clickjacking, MIME sniffing, and protocol downgrade attacks.

This guide gives you complete, copy-paste configurations for every important security header - both via .htaccess (for shared hosting environments) and httpd.conf (for servers you control directly). Scan your domain on SecHead to see your current grade before and after applying these changes.


Prerequisites

Enable mod_headers

Security headers in Apache are set using the mod_headers module. Confirm it's enabled:

bash
apache2ctl -M | grep headers
# or
httpd -M | grep headers

If it's not listed, enable it:

bash
# Debian/Ubuntu
sudo a2enmod headers
sudo systemctl restart apache2

# RHEL/CentOS/Fedora
# Edit /etc/httpd/conf.modules.d/00-base.conf and uncomment:
# LoadModule headers_module modules/mod_headers.so
sudo systemctl restart httpd

Enable mod_rewrite (for HTTPS redirect)

bash
sudo a2enmod rewrite
sudo systemctl restart apache2

The Complete Security Headers Block

For .htaccess (shared hosting / per-directory)

apache
# -------------------------------------------------------
# Security Headers - SecHead recommended configuration
# Apache 2.4 / .htaccess
# -------------------------------------------------------

# Prevent MIME-type sniffing
Header always set X-Content-Type-Options "nosniff"

# Clickjacking protection
Header always set X-Frame-Options "DENY"

# HTTPS enforcement (only set on HTTPS sites)
# max-age=31536000 = 1 year; add includeSubDomains if all subdomains use HTTPS
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

# Control referrer information sent in requests
Header always set Referrer-Policy "strict-origin-when-cross-origin"

# Restrict browser feature access
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"

# Content Security Policy - adjust sources to match your actual dependencies
Header always set Content-Security-Policy "default-src 'none'; 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';"

# Remove server fingerprinting headers
Header always unset X-Powered-By
Header always unset Server

For httpd.conf or a VirtualHost block

apache
<VirtualHost *:443>
    ServerName example.com

    # ... SSL configuration ...

    # Security headers
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "DENY"
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"
    Header always set Content-Security-Policy "default-src 'none'; 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 unset X-Powered-By
    Header always unset Server
</VirtualHost>

Header-by-Header Breakdown

Strict-Transport-Security (HSTS)

HSTS tells browsers to only connect to your site over HTTPS - even if the user types http://. Once a browser sees this header, it will automatically upgrade every future request without a round-trip to the server.

apache
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

Important: Only add this header to your HTTPS VirtualHost, never the HTTP one. If you add it to port 80, browsers may lock users out if your SSL certificate expires.

Force the HTTP → HTTPS redirect first:

apache
<VirtualHost *:80>
    ServerName example.com
    RewriteEngine On
    RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
</VirtualHost>

For more detail, see the complete HSTS guide.


Content-Security-Policy

CSP is the most powerful - and most complex - header. The snippet above uses a strict default-src 'none' baseline. You'll need to expand it to match your actual site:

If you load scripts from a CDN:

apache
Header always set Content-Security-Policy "default-src 'none'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self'; frame-ancestors 'none';"

If you use inline scripts (requires nonces via PHP/server-side generation):

php
<?php
$nonce = base64_encode(random_bytes(16));
header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic'; style-src 'self'; ...");
?>
<script nonce="<?= htmlspecialchars($nonce) ?>">
  // your inline script
</script>

See the CSP explained guide for the full breakdown of directives.


X-Frame-Options

Prevents your pages from being embedded in iframes on other domains, blocking clickjacking attacks.

apache
Header always set X-Frame-Options "DENY"

Use SAMEORIGIN instead of DENY if your own site uses iframes internally (e.g., an embedded payment widget within the same domain).

Note: Modern CSP frame-ancestors supersedes X-Frame-Options. Include both for maximum compatibility:

apache
Header always set X-Frame-Options "DENY"
# frame-ancestors is already in the CSP block above

X-Content-Type-Options

Prevents the browser from "sniffing" the MIME type of a response. Without this, a browser might interpret a .jpg upload that contains HTML as a valid HTML page - opening a vector for XSS via malicious file uploads.

apache
Header always set X-Content-Type-Options "nosniff"

This is the simplest header to add and has zero compatibility risk.


Referrer-Policy

Controls how much of the current page URL is sent in the Referer header when a user clicks a link to another site.

apache
Header always set Referrer-Policy "strict-origin-when-cross-origin"

This setting sends the full URL within your own site (useful for analytics) but only sends the domain (not the path or query string) to external sites. This prevents leaking sensitive URL parameters (session IDs, private tokens) to third parties.


Permissions-Policy

Restricts which browser APIs your site is permitted to use. Useful for preventing third-party scripts (ads, analytics, embeds) from accessing cameras, microphones, or location data you didn't intend to grant.

apache
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()"

Adjust the list based on features your site actually uses - if you have a contact form with geolocation, you'd change geolocation=() to geolocation=(self).


Removing Fingerprinting Headers

Apache by default sends Server: Apache/2.4.x (Ubuntu) and may send X-Powered-By from PHP. These tell attackers exactly what software version you're running, making targeted exploit searches easier.

Remove from headers

apache
Header always unset Server
Header always unset X-Powered-By

Suppress the Server version in httpd.conf

apache
ServerTokens Prod
ServerSignature Off

ServerTokens Prod reduces the Server header value to just Apache with no version. ServerSignature Off removes the version footer from Apache-generated error pages.


Testing Your Configuration

Syntax check before restarting

bash
# Apache 2.4 on Debian/Ubuntu
apache2ctl configtest

# RHEL/CentOS
httpd -t

Fix any Syntax error output before restarting. A broken config will take your server down.

Reload (not restart) for zero-downtime application

bash
sudo systemctl reload apache2
# or
sudo apachectl graceful

Verify headers are being sent

bash
curl -I https://example.com

You should see your new headers in the output. Then run a full scan on SecHead to get your grade and confirm nothing is missing.


Common Issues

Headers not appearing on static files

If your static files are served directly by Apache without going through PHP or another handler, verify that mod_headers is loaded and that your .htaccess is in the correct directory. The Header always set directive (with always) ensures headers are added even on error responses.

HSTS locking you out

If you accidentally set HSTS on an HTTP-only server or a server with an invalid certificate, browsers will refuse to connect. Recovery requires the user to manually clear HSTS state from their browser's security settings. Always test on staging before applying HSTS to production.

CSP blocking legitimate resources

Use Content-Security-Policy-Report-Only first (same syntax, different header name) to test your policy without blocking anything. This sends violations to your console without breaking the page.


Full Reference Configuration

apache
# -------------------------------------------------------
# Complete Apache Security Headers - copy-paste ready
# Tested on Apache 2.4 with mod_headers enabled
# -------------------------------------------------------

# HTTPS redirect (HTTP VirtualHost)
<VirtualHost *:80>
    ServerName example.com
    RewriteEngine On
    RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
</VirtualHost>

# Main secure site (HTTPS VirtualHost)
<VirtualHost *:443>
    ServerName example.com

    SSLEngine on
    SSLCertificateFile    /path/to/cert.pem
    SSLCertificateKeyFile /path/to/key.pem

    # Security headers
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "DENY"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"
    Header always set Content-Security-Policy "default-src 'none'; 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';"

    # Remove fingerprinting headers
    Header always unset X-Powered-By
    Header always unset Server

    DocumentRoot /var/www/example.com
</VirtualHost>
apache
# httpd.conf additions
ServerTokens Prod
ServerSignature Off

After applying this configuration, run a SecHead scan on your domain to confirm your grade. For the equivalent configuration on other platforms, see the security headers for Next.js guide and the WordPress security headers guide. For the full explanation of what each header does and why it matters, the complete security headers checklist covers every header in one place.

Related articles

Free tool

Check your own security headers

Instant grade, plain-language explanations, and a full remediation plan - no signup needed.

Scan your site now →