/ Security Audit Fixes / Apache

How to Fix Security Headers in Bare Apache

Apache 2.4 still runs huge swathes of the web — especially on shared hosting and long-running CentOS/RHEL servers. This guide walks through every fix our Security Audit raises on a directly-configured Apache server: enabling mod_headers, adding the six recommended HTTP security headers via vhost or .htaccess, configuring modern TLS via mod_ssl, and publishing security.txt. Tested against Apache 2.4.62 on Ubuntu 22.04 and RHEL 9. For nginx, see the nginx variant; for the full finding catalogue, see Security Audit Fixes.

1. Enable mod_headers and mod_ssl

Apache's Header directive lives in mod_headers — not loaded by default on every distribution. mod_ssl handles HTTPS. Both must be active before any header or TLS config takes effect.

Step 1
Enable the modules (Debian/Ubuntu)
sudo a2enmod headers ssl
sudo systemctl restart apache2
On RHEL/CentOS the syntax differs:
sudo dnf install mod_ssl
# headers module is usually compiled in; verify with:
sudo apachectl -M | grep -E "headers|ssl"
If headers_module is missing on RHEL, add a LoadModule line to /etc/httpd/conf.modules.d/00-base.conf.

2. Add the six HTTP security headers

You have two placement choices: in the vhost config (faster, requires server access) or in .htaccess (slower, works on shared hosting). The Header directives are identical in both.

⚠️ Use Header always set, not Header set. The plain form only applies to 2xx/3xx responses — security headers are stripped from 4xx and 5xx errors, exactly when an attacker is probing your server.

Option A: vhost config (recommended)

Step 1
Edit your SSL vhost
Typical paths:
/etc/apache2/sites-available/yourdomain-le-ssl.conf    # Debian/Ubuntu with Certbot
/etc/httpd/conf.d/ssl.conf                              # RHEL/CentOS default
/etc/httpd/conf.d/yourdomain.conf                       # custom RHEL configs
Open the file:
sudo nano /etc/apache2/sites-available/yourdomain-le-ssl.conf
Step 2
Add the six directives inside <VirtualHost *:443>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'self'"

Option B: .htaccess (shared hosting fallback)

Step 1
Wrap in IfModule
In .htaccess at your document root:
<IfModule mod_headers.c>
  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
  Header always set X-Frame-Options "SAMEORIGIN"
  Header always set X-Content-Type-Options "nosniff"
  Header always set Referrer-Policy "strict-origin-when-cross-origin"
  Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
  Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'self'"
</IfModule>
The IfModule wrap prevents a 500 error if mod_headers is disabled.
💡 Vhost config is parsed once at Apache startup. .htaccess is parsed on every single request. On a busy site, vhost saves measurable CPU.

3. Configure modern TLS protocols

Apache defaults vary by distribution and version. Don't assume your server already disables TLS 1.0/1.1.

Step 1
Set protocols and ciphers
Inside the SSL vhost (or in /etc/apache2/mods-available/ssl.conf for site-wide):
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLHonorCipherOrder off
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305
SSLSessionTickets off
SSLHonorCipherOrder off is correct for modern TLS — clients now pick better ciphers than servers do.
Step 2
Test and reload
sudo apachectl configtest
sudo systemctl reload apache2
Then run SSL Labs SSL Test against your domain. Target: A+ (HSTS preload requirement is already met from section 2).

4. Publish security.txt

Step 1
Create the file
sudo mkdir -p /var/www/yourdomain/public/.well-known
sudo nano /var/www/yourdomain/public/.well-known/security.txt
Contents per RFC 9116:
Contact: mailto:security@yourdomain.com
Expires: 2027-05-18T00:00:00.000Z
Preferred-Languages: en
Canonical: https://yourdomain.com/.well-known/security.txt
Step 2
Force text/plain Content-Type
In vhost or .htaccess:
<Files "security.txt">
  ForceType text/plain
</Files>
Step 3
Verify
curl -sI https://yourdomain.com/.well-known/security.txt
Expected: HTTP/2 200 and content-type: text/plain.

5. Verify everything

Run the full check:

curl -sI https://yourdomain.com/ | grep -iE "strict-transport|x-frame|x-content|referrer|permissions|content-security"

All six headers should appear. Then re-run the Security Audit for the full report — most bare-Apache servers reach 95-100/100 after this guide.

🛡 Re-run the audit

Verify every header, TLS protocol and security.txt with a fresh scan.

Run Security Audit →
Related Guides: Security Audit Fixes  ·  Fix in nginx  ·  Fix via Cloudflare  ·  Security Audit Guide
💬 Got a problem?