Configure essential security headers including HSTS, CSRF protection, and Content Security Policy (CSP) in Apache to protect your web applications from common attacks like XSS, clickjacking, and data injection vulnerabilities.
Prerequisites
- Apache web server installed and running
- Root or sudo access
- Basic understanding of web security concepts
- SSL certificates configured (recommended)
What this solves
Web applications face constant threats from cross-site scripting (XSS), clickjacking, and data injection attacks. HTTP security headers provide your first line of defense by instructing browsers how to handle your content safely. This tutorial configures Apache with essential security headers including Content Security Policy (CSP), HSTS, and anti-clickjacking protections to significantly reduce your attack surface.
Step-by-step configuration
Enable required Apache modules
Apache needs the headers module to add security headers and the security module for advanced CSP features. Enable both modules to get started.
sudo a2enmod headers
sudo a2enmod security2
sudo httpd -M | grep -E "(headers|security)"
# If not loaded, check /etc/httpd/conf.modules.d/00-base.conf
sudo systemctl reload httpd
Install ModSecurity for CSP enforcement
ModSecurity provides advanced Content Security Policy features and violation reporting. Install it to enable comprehensive CSP protection.
sudo apt update
sudo apt install -y libapache2-mod-security2
sudo a2enmod security2
sudo dnf install -y epel-release
sudo dnf install -y mod_security
Create security headers configuration
Create a dedicated configuration file for security headers. This keeps your security settings organized and makes them easy to manage across virtual hosts.
# Security Headers Configuration
# Enable security headers for all responses
Configure ModSecurity for CSP reporting
Set up ModSecurity to handle CSP violation reports. This helps you identify and fix CSP issues in your applications.
# Enable ModSecurity engine
SecRuleEngine On
# Configure audit logging
SecAuditEngine RelevantOnly
SecAuditLog /var/log/apache2/modsec_audit.log
# CSP violation logging
SecRule REQUEST_URI "@streq /csp-report" \
"id:1001,phase:1,pass,nolog,ctl:ruleEngine=Off"
# Log CSP violations
SecRule REQUEST_METHOD "@streq POST" \
"chain,id:1002,phase:2,msg:'CSP Violation Report',logdata:'%{REQUEST_BODY}'"
SecRule REQUEST_URI "@streq /csp-report" \
"t:none"
# Enable ModSecurity engine
SecRuleEngine On
# Configure audit logging
SecAuditEngine RelevantOnly
SecAuditLog /var/log/httpd/modsec_audit.log
# CSP violation logging
SecRule REQUEST_URI "@streq /csp-report" \
"id:1001,phase:1,pass,nolog,ctl:ruleEngine=Off"
# Log CSP violations
SecRule REQUEST_METHOD "@streq POST" \
"chain,id:1002,phase:2,msg:'CSP Violation Report',logdata:'%{REQUEST_BODY}'"
SecRule REQUEST_URI "@streq /csp-report" \
"t:none"
Create CSP violation report handler
Create a simple endpoint to receive and log CSP violation reports. This helps you monitor policy violations and adjust your CSP as needed.
'Method not allowed']));
}
// Get the raw POST data
$input = file_get_contents('php://input');
$data = json_decode($input, true);
if ($data && isset($data['csp-report'])) {
$report = $data['csp-report'];
// Log the violation
$logEntry = [
'timestamp' => date('c'),
'blocked-uri' => $report['blocked-uri'] ?? 'unknown',
'document-uri' => $report['document-uri'] ?? 'unknown',
'violated-directive' => $report['violated-directive'] ?? 'unknown',
'source-file' => $report['source-file'] ?? 'unknown',
'line-number' => $report['line-number'] ?? 'unknown'
];
// Write to log file
file_put_contents(
'/var/log/apache2/csp-violations.log',
json_encode($logEntry) . "\n",
FILE_APPEND | LOCK_EX
);
http_response_code(204); // No content
} else {
http_response_code(400);
exit(json_encode(['error' => 'Invalid report format']));
}
?>
Enable security headers configuration
Enable the security headers configuration and restart Apache to apply the changes.
sudo a2enconf security-headers
sudo apache2ctl configtest
sudo systemctl restart apache2
sudo httpd -t
sudo systemctl restart httpd
Configure application-specific CSP
Customize the Content Security Policy for your specific application. This example shows a more restrictive policy for a typical web application.
Set up log rotation for security logs
Configure log rotation for CSP violation logs and ModSecurity audit logs to prevent disk space issues.
/var/log/apache2/csp-violations.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 640 www-data adm
postrotate
systemctl reload apache2 > /dev/null 2>&1 || true
endscript
}
/var/log/apache2/modsec_audit.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 640 www-data adm
postrotate
systemctl reload apache2 > /dev/null 2>&1 || true
endscript
}
Verify your setup
Test your security headers configuration to ensure everything is working correctly.
# Check Apache configuration
sudo apache2ctl configtest
# Test security headers with curl
curl -I https://example.com
# Specifically check for security headers
curl -s -D- https://example.com | grep -E "(Strict-Transport|Content-Security|X-Frame|X-Content-Type)"
# Test CSP violation reporting
curl -X POST https://example.com/csp-report \
-H "Content-Type: application/json" \
-d '{"csp-report":{"blocked-uri":"https://evil.com/script.js","document-uri":"https://example.com/test","violated-directive":"script-src"}}'
# Check CSP violation logs
sudo tail -f /var/log/apache2/csp-violations.log
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Scripts not loading | CSP blocks inline or external scripts | Add script sources to script-src directive or use nonces |
| Styles not applying | CSP blocks inline styles | Use external stylesheets or add 'unsafe-inline' to style-src |
| Images not displaying | Missing image sources in CSP | Add image domains to img-src directive |
| HSTS not working | Site accessed over HTTP | Ensure redirects to HTTPS and headers only sent over HTTPS |
| CSP reports not logging | Incorrect report-uri or permissions | Check PHP file permissions and log directory writability |
| Headers module not loaded | Module not enabled | Run sudo a2enmod headers and restart Apache |
Advanced CSP configuration
For production applications, consider these advanced CSP strategies for better security without breaking functionality.
Use nonces for inline scripts
Generate unique nonces for each request to allow specific inline scripts while blocking others. This provides better security than 'unsafe-inline'.
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Global variables
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BACKUP_DIR="/opt/apache-security-backup-$(date +%Y%m%d-%H%M%S)"
# Cleanup function for rollback
cleanup() {
if [ -d "$BACKUP_DIR" ]; then
echo -e "${YELLOW}[ROLLBACK] Restoring from backup...${NC}"
if [ -f "$BACKUP_DIR/apache.conf" ]; then
cp "$BACKUP_DIR"/* "$APACHE_CONF_DIR/" 2>/dev/null || true
fi
systemctl restart "$APACHE_SERVICE" 2>/dev/null || true
fi
}
trap cleanup ERR
usage() {
echo "Usage: $0 [OPTIONS]"
echo "Configure Apache security headers and Content Security Policy"
echo ""
echo "Options:"
echo " -d, --domain DOMAIN Domain name for HSTS preload (optional)"
echo " -h, --help Show this help message"
echo ""
echo "Example:"
echo " $0 --domain example.com"
exit 1
}
log_step() {
echo -e "${GREEN}$1${NC}"
}
log_warning() {
echo -e "${YELLOW}$1${NC}"
}
log_error() {
echo -e "${RED}$1${NC}"
}
check_prerequisites() {
log_step "[1/8] Checking prerequisites..."
if [ "$EUID" -ne 0 ]; then
log_error "This script must be run as root or with sudo"
exit 1
fi
if ! command -v systemctl &> /dev/null; then
log_error "systemctl is required but not found"
exit 1
fi
}
detect_distro() {
log_step "[2/8] Detecting distribution..."
if [ ! -f /etc/os-release ]; then
log_error "Cannot detect distribution - /etc/os-release not found"
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update"
APACHE_SERVICE="apache2"
APACHE_CONF_DIR="/etc/apache2"
APACHE_SITES_DIR="/etc/apache2/sites-available"
APACHE_MODS_DIR="/etc/apache2/mods-available"
APACHE_LOG_DIR="/var/log/apache2"
MODSEC_CONF="/etc/modsecurity/modsecurity.conf"
A2ENMOD_CMD="a2enmod"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
APACHE_SERVICE="httpd"
APACHE_CONF_DIR="/etc/httpd"
APACHE_SITES_DIR="/etc/httpd/conf.d"
APACHE_MODS_DIR="/etc/httpd/conf.modules.d"
APACHE_LOG_DIR="/var/log/httpd"
MODSEC_CONF="/etc/httpd/modsecurity.d/modsecurity.conf"
A2ENMOD_CMD=""
;;
fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
APACHE_SERVICE="httpd"
APACHE_CONF_DIR="/etc/httpd"
APACHE_SITES_DIR="/etc/httpd/conf.d"
APACHE_MODS_DIR="/etc/httpd/conf.modules.d"
APACHE_LOG_DIR="/var/log/httpd"
MODSEC_CONF="/etc/httpd/modsecurity.d/modsecurity.conf"
A2ENMOD_CMD=""
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
APACHE_SERVICE="httpd"
APACHE_CONF_DIR="/etc/httpd"
APACHE_SITES_DIR="/etc/httpd/conf.d"
APACHE_MODS_DIR="/etc/httpd/conf.modules.d"
APACHE_LOG_DIR="/var/log/httpd"
MODSEC_CONF="/etc/httpd/conf.d/modsecurity.conf"
A2ENMOD_CMD=""
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
echo "Detected: $PRETTY_NAME"
}
create_backup() {
log_step "[3/8] Creating backup..."
mkdir -p "$BACKUP_DIR"
if [ -f "$APACHE_CONF_DIR/apache2.conf" ]; then
cp "$APACHE_CONF_DIR/apache2.conf" "$BACKUP_DIR/" 2>/dev/null || true
fi
if [ -f "$APACHE_CONF_DIR/conf/httpd.conf" ]; then
cp "$APACHE_CONF_DIR/conf/httpd.conf" "$BACKUP_DIR/" 2>/dev/null || true
fi
echo "Backup created in: $BACKUP_DIR"
}
install_packages() {
log_step "[4/8] Installing required packages..."
$PKG_UPDATE
case "$ID" in
ubuntu|debian)
$PKG_INSTALL apache2 libapache2-mod-security2
if [ -n "$A2ENMOD_CMD" ]; then
$A2ENMOD_CMD headers
$A2ENMOD_CMD security2
fi
;;
almalinux|rocky|centos|rhel|ol)
$PKG_INSTALL epel-release
$PKG_INSTALL httpd mod_security
;;
fedora)
$PKG_INSTALL httpd mod_security
;;
amzn)
$PKG_INSTALL httpd mod_security
;;
esac
}
configure_security_headers() {
log_step "[5/8] Configuring security headers..."
local security_conf="$APACHE_SITES_DIR/security-headers.conf"
cat > "$security_conf" << 'EOF'
# Security Headers Configuration
<IfModule mod_headers.c>
# HTTP Strict Transport Security (HSTS)
# Forces HTTPS connections for 1 year, includes subdomains
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Prevent MIME type sniffing
Header always set X-Content-Type-Options "nosniff"
# Enable XSS protection in browsers
Header always set X-XSS-Protection "1; mode=block"
# Prevent clickjacking attacks
Header always set X-Frame-Options "SAMEORIGIN"
# Control referrer information
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Remove server information
Header always unset Server
Header always unset X-Powered-By
# Permissions Policy (formerly Feature Policy)
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"
# Basic CSP - adjust sources based on your application needs
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'"
</IfModule>
EOF
chown root:root "$security_conf"
chmod 644 "$security_conf"
# Enable the configuration on Debian-based systems
if [ "$ID" = "ubuntu" ] || [ "$ID" = "debian" ]; then
if [ -f "$APACHE_SITES_DIR/security-headers.conf" ]; then
a2ensite security-headers.conf 2>/dev/null || true
fi
fi
}
configure_modsecurity() {
log_step "[6/8] Configuring ModSecurity..."
# Ensure ModSecurity directories exist
local modsec_dir
case "$ID" in
ubuntu|debian)
modsec_dir="/etc/modsecurity"
;;
*)
modsec_dir="/etc/httpd/modsecurity.d"
mkdir -p "$modsec_dir"
;;
esac
# Create custom ModSecurity rules for CSP
cat > "$modsec_dir/csp-rules.conf" << 'EOF'
# Enable ModSecurity engine
SecRuleEngine On
# Configure audit logging
SecAuditEngine RelevantOnly
# CSP violation logging
SecRule REQUEST_URI "@streq /csp-report" \
"id:1001,phase:1,pass,nolog,ctl:ruleEngine=Off"
# Log CSP violations
SecRule REQUEST_METHOD "@streq POST" \
"chain,id:1002,phase:2,msg:'CSP Violation Report',logdata:'%{REQUEST_BODY}'"
SecRule REQUEST_URI "@streq /csp-report" \
"t:none"
EOF
chown root:root "$modsec_dir/csp-rules.conf"
chmod 644 "$modsec_dir/csp-rules.conf"
# Set audit log path
local audit_log="$APACHE_LOG_DIR/modsec_audit.log"
sed -i "s|SecAuditLog .*|SecAuditLog $audit_log|" "$modsec_dir/csp-rules.conf"
# Create log file with proper permissions
touch "$audit_log"
chown apache:apache "$audit_log" 2>/dev/null || chown www-data:www-data "$audit_log" 2>/dev/null || true
chmod 644 "$audit_log"
}
configure_apache_modules() {
log_step "[7/8] Configuring Apache modules..."
case "$ID" in
almalinux|rocky|centos|rhel|ol|fedora|amzn)
# Ensure modules are loaded in RHEL-based systems
if ! grep -q "LoadModule headers_module" "$APACHE_MODS_DIR/00-base.conf" 2>/dev/null; then
echo "LoadModule headers_module modules/mod_headers.so" >> "$APACHE_MODS_DIR/00-base.conf"
fi
if ! grep -q "LoadModule security2_module" "$APACHE_MODS_DIR/00-base.conf" 2>/dev/null; then
echo "LoadModule security2_module modules/mod_security2.so" >> "$APACHE_MODS_DIR/00-base.conf"
fi
;;
esac
}
restart_and_verify() {
log_step "[8/8] Restarting Apache and verifying configuration..."
# Test Apache configuration
if ! apache2ctl configtest 2>/dev/null && ! httpd -t 2>/dev/null; then
log_error "Apache configuration test failed"
return 1
fi
# Restart Apache service
systemctl restart "$APACHE_SERVICE"
systemctl enable "$APACHE_SERVICE"
# Verify modules are loaded
if command -v apache2ctl &> /dev/null; then
apache2ctl -M | grep -E "(headers|security)" || true
else
httpd -M | grep -E "(headers|security)" || true
fi
echo -e "${GREEN}Security headers configuration completed successfully!${NC}"
echo ""
echo "Next steps:"
echo "1. Test your security headers at: https://securityheaders.com/"
echo "2. Adjust CSP policies in $APACHE_SITES_DIR/security-headers.conf based on your application needs"
echo "3. Monitor CSP violations in $APACHE_LOG_DIR/modsec_audit.log"
echo "4. Consider implementing HSTS preload by submitting your domain to https://hstspreload.org/"
}
# Parse command line arguments
DOMAIN=""
while [[ $# -gt 0 ]]; do
case $1 in
-d|--domain)
DOMAIN="$2"
shift 2
;;
-h|--help)
usage
;;
*)
echo "Unknown option: $1"
usage
;;
esac
done
# Main execution
main() {
echo "Apache Security Headers Configuration Script"
echo "==========================================="
check_prerequisites
detect_distro
create_backup
install_packages
configure_security_headers
configure_modsecurity
configure_apache_modules
restart_and_verify
if [ -n "$DOMAIN" ]; then
log_warning "Remember to submit $DOMAIN to HSTS preload list at https://hstspreload.org/"
fi
}
main "$@"
Review the script before running. Execute with: bash install.sh