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
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
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
# 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=()"
IfModule>
Content Security Policy Configuration
# 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'"
# CSP Report URI for violation reporting (optional)
# Header always set Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report"
IfModule>
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"
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
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.
ServerName example.com
DocumentRoot /var/www/example.com
# SSL Configuration
SSLEngine on
SSLCertificateFile /etc/ssl/certs/example.com.crt
SSLCertificateKeyFile /etc/ssl/private/example.com.key
# Include global security headers
Include conf-available/security-headers.conf
# Application-specific CSP
# Strict CSP for this application
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://js.stripe.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https://images.example.com; connect-src 'self' https://api.example.com; frame-src https://js.stripe.com; report-uri /csp-report"
# Additional security for sensitive applications
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
Header always set Cross-Origin-Resource-Policy "same-origin"
IfModule>
# Security configurations
Options -Indexes -Includes -ExecCGI
AllowOverride None
Require all granted
# Disable server-info and server-status
Require all denied
Require all denied
VirtualHost>
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'.