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.
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.
sudo a2enmod headers ssl sudo systemctl restart apache2On 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.
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.
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./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 configsOpen the file:
sudo nano /etc/apache2/sites-available/yourdomain-le-ssl.conf
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'"
.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.
.htaccess is parsed on every single request. On a busy site, vhost saves measurable CPU.Apache defaults vary by distribution and version. Don't assume your server already disables TLS 1.0/1.1.
/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.
sudo apachectl configtest sudo systemctl reload apache2Then run SSL Labs SSL Test against your domain. Target: A+ (HSTS preload requirement is already met from section 2).
sudo mkdir -p /var/www/yourdomain/public/.well-known sudo nano /var/www/yourdomain/public/.well-known/security.txtContents 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
.htaccess:
<Files "security.txt"> ForceType text/plain </Files>
curl -sI https://yourdomain.com/.well-known/security.txtExpected:
HTTP/2 200 and content-type: text/plain.
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.
Verify every header, TLS protocol and security.txt with a fresh scan.
Run Security Audit →