/ Security Audit Fixes / Apache

How to Fix Security Headers in Apache (Bare Server)

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?

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.

Frequently asked questions

Should I put security headers in .htaccess or vhost config?
Vhost config is faster (Apache parses it once at startup, not on every request). .htaccess is only better if you do not have access to vhost config — typical on shared hosting. If you control the server, always prefer vhost config. The Header directives work identically in both.
Why does 'Header set' not work but 'Header always set' does?
'Header set' only applies to 2xx and 3xx responses. 'Header always set' applies to every response including 4xx and 5xx errors. For security headers you always want 'always' — an attacker probing your server is precisely when you need HSTS and CSP to apply.
Why is mod_headers not loaded by default on RHEL/CentOS?
On RHEL-family systems mod_headers is typically compiled in but not loaded. Check /etc/httpd/conf.modules.d/ for a file containing LoadModule headers_module modules/mod_headers.so. If missing, add it and restart httpd.
How do I serve security.txt as text/plain in Apache?
Inside the vhost or in .htaccess: ForceType text/plain . This overrides any inherited Content-Type for that specific file. Alternatively, place it in a directory with AddType text/plain .txt scoped to /.well-known/.