Apache and Nginx Hardening
Web server hardening is a critical security practice that protects your infrastructure from cyberattacks, data breaches, and unauthorized access. Both Apache and Nginx are powerful web servers, but their default configurations often prioritize ease of use over security. This comprehensive guide covers essential hardening techniques for both Apache and Nginx, helping you build a robust security posture that protects your applications, data, and users from evolving threats.
Server hardening involves implementing multiple layers of security controls including hiding server information, restricting access, disabling unnecessary features, implementing secure communication protocols, and configuring proper file permissions. Whether you're running e-commerce platforms, corporate websites, or API services, proper web server hardening is fundamental to your overall security strategy and compliance requirements.
Table of Contents
- Prerequisites
- Understanding Web Server Hardening
- Apache Hardening
- Nginx Hardening
- Common Security Measures
- SSL/TLS Hardening
- Access Control
- Security Headers
- Verification and Testing
- Troubleshooting
- Best Practices
- Conclusion
Prerequisites
Before hardening your web servers, ensure you have:
- Operating System: Ubuntu 20.04/22.04, Debian 10/11, CentOS 8/Rocky Linux 8, or similar
- Web Server: Apache 2.4+ or Nginx 1.18+ installed and running
- Root or sudo access: Required for modifying system and server configurations
- Backup: Complete backup of current configuration files
- SSL/TLS Certificate: Valid SSL certificate for HTTPS implementation
- Basic knowledge: Understanding of HTTP, web server configuration, and Linux administration
- Testing environment: Recommended to test changes before applying to production
Understanding Web Server Hardening
What is Web Server Hardening?
Web server hardening is the process of enhancing server security by reducing its attack surface, removing unnecessary services, implementing security controls, and following security best practices. The goal is to minimize vulnerabilities that attackers could exploit.
Key objectives:
- Minimize attack surface by disabling unnecessary features
- Implement principle of least privilege
- Encrypt data in transit with strong SSL/TLS
- Hide server information from attackers
- Implement access controls and authentication
- Monitor and log security events
- Keep software updated with security patches
Common Web Server Vulnerabilities
Information disclosure: Server version and configuration information exposed Weak SSL/TLS: Outdated protocols and weak cipher suites Directory traversal: Unauthorized access to file system Clickjacking: Malicious iframe embedding Cross-Site Scripting (XSS): Client-side script injection DDoS attacks: Resource exhaustion attacks Misconfiguration: Insecure default settings Outdated software: Known vulnerabilities in old versions
Apache Hardening
Hide Apache Version and OS
Prevent information disclosure by hiding server details:
# Edit Apache security configuration
sudo nano /etc/apache2/conf-available/security.conf
Add or modify:
# Hide Apache version
ServerTokens Prod
# Hide server signature
ServerSignature Off
# Disable TRACE HTTP method (prevents XST attacks)
TraceEnable Off
Enable configuration and restart:
sudo a2enconf security
sudo systemctl restart apache2
Disable Unnecessary Modules
Reduce attack surface by disabling unused modules:
# List enabled modules
apache2ctl -M
# Disable unnecessary modules
sudo a2dismod status # Server status
sudo a2dismod userdir # User directories
sudo a2dismod autoindex # Directory listing
sudo a2dismod cgi # CGI execution
sudo a2dismod dav # WebDAV
sudo a2dismod dav_fs # WebDAV filesystem
# Keep only essential modules:
# - ssl (for HTTPS)
# - rewrite (for URL rewriting)
# - headers (for security headers)
# - expires (for caching)
# Restart Apache
sudo systemctl restart apache2
Directory Permissions and Options
Configure strict directory permissions:
# Edit main configuration or virtual host
sudo nano /etc/apache2/apache2.conf
# Default restrictive policy
<Directory />
Options None
AllowOverride None
Require all denied
</Directory>
# Web root configuration
<Directory /var/www/html>
# Disable directory listing
Options -Indexes -Includes -ExecCGI
# Allow symbolic links (if needed)
# Options +FollowSymLinks
# Disable .htaccess if not needed (better performance)
AllowOverride None
# Or allow specific overrides only
# AllowOverride FileInfo AuthConfig
# Access control
Require all granted
# Limit HTTP methods
<LimitExcept GET POST HEAD>
Require all denied
</LimitExcept>
</Directory>
# Protect sensitive files
<FilesMatch "^\.ht">
Require all denied
</FilesMatch>
# Protect backup and config files
<FilesMatch "\.(bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)$">
Require all denied
</FilesMatch>
Limit Request Size
Prevent DoS attacks by limiting request sizes:
sudo nano /etc/apache2/apache2.conf
# Limit request body size (10MB example)
LimitRequestBody 10485760
# Limit request field size
LimitRequestFieldSize 8190
# Limit number of request fields
LimitRequestFields 100
# Limit request line size
LimitRequestLine 8190
# Timeout configurations
Timeout 60
KeepAliveTimeout 5
Configure ModSecurity (Web Application Firewall)
Install and configure ModSecurity:
# Install ModSecurity
sudo apt install libapache2-mod-security2 -y
# Enable module
sudo a2enmod security2
# Copy recommended configuration
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
# Edit configuration
sudo nano /etc/modsecurity/modsecurity.conf
# Enable ModSecurity
SecRuleEngine On
# Set audit log
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
SecAuditLogParts ABIJDEFHZ
SecAuditLogType Serial
SecAuditLog /var/log/apache2/modsec_audit.log
# Request body access
SecRequestBodyAccess On
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
# Response body access
SecResponseBodyAccess On
SecResponseBodyLimit 524288
Install OWASP Core Rule Set:
# Download OWASP CRS
cd /tmp
wget https://github.com/coreruleset/coreruleset/archive/v3.3.4.tar.gz
tar -xvzf v3.3.4.tar.gz
sudo mv coreruleset-3.3.4 /etc/modsecurity/crs
cd /etc/modsecurity/crs
sudo cp crs-setup.conf.example crs-setup.conf
# Load CRS rules
sudo nano /etc/apache2/mods-enabled/security2.conf
Add:
IncludeOptional /etc/modsecurity/*.conf
IncludeOptional /etc/modsecurity/crs/crs-setup.conf
IncludeOptional /etc/modsecurity/crs/rules/*.conf
# Restart Apache
sudo systemctl restart apache2
User and Group Configuration
Run Apache with least privileges:
# Edit Apache configuration
sudo nano /etc/apache2/envvars
# Use dedicated non-privileged user
export APACHE_RUN_USER=www-data
export APACHE_RUN_GROUP=www-data
Set proper file ownership:
# Set ownership of web files
sudo chown -R www-data:www-data /var/www/html
# Set directory permissions (755)
sudo find /var/www/html -type d -exec chmod 755 {} \;
# Set file permissions (644)
sudo find /var/www/html -type f -exec chmod 644 {} \;
Nginx Hardening
Hide Nginx Version
Prevent version information disclosure:
# Edit main Nginx configuration
sudo nano /etc/nginx/nginx.conf
http {
# Hide Nginx version
server_tokens off;
# Additional headers will be configured per server block
}
Disable Unnecessary Modules
Nginx is modular; compile only needed modules or use dynamic modules:
# Check compiled modules
nginx -V 2>&1 | grep -o with-[a-z_-]*
# For custom builds, recompile without unnecessary modules
# Example: ./configure --without-http_autoindex_module --without-http_ssi_module
Configure Server Blocks Securely
Implement secure server block configuration:
sudo nano /etc/nginx/sites-available/default
server {
listen 80;
server_name example.com www.example.com;
# Redirect to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
root /var/www/html;
index index.html index.php;
# Hide Nginx version
server_tokens off;
# Security headers (will configure later)
include /etc/nginx/snippets/security-headers.conf;
# SSL configuration (will configure later)
include /etc/nginx/snippets/ssl-params.conf;
# Disable unwanted HTTP methods
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
return 405;
}
# Limit request size
client_max_body_size 10M;
client_body_buffer_size 128k;
# Timeouts
client_body_timeout 10s;
client_header_timeout 10s;
keepalive_timeout 5s 5s;
send_timeout 10s;
# Disable directory listing
autoindex off;
# Protect sensitive files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Protect backup and config files
location ~* \.(bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)$ {
deny all;
}
# Main location
location / {
try_files $uri $uri/ =404;
}
# PHP processing (if needed)
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
# Security for PHP
fastcgi_param PHP_VALUE "open_basedir=/var/www/html:/tmp";
fastcgi_param PHP_ADMIN_VALUE "disable_functions=exec,passthru,shell_exec,system";
}
# Deny access to uploads directory PHP execution
location ~* /uploads/.*\.php$ {
deny all;
}
# Static files caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# Logs
access_log /var/log/nginx/example.com-access.log;
error_log /var/log/nginx/example.com-error.log;
}
Rate Limiting
Implement rate limiting to prevent abuse:
sudo nano /etc/nginx/nginx.conf
http {
# Define rate limit zones
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
# Connection limits
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# In server block:
server {
# Apply general rate limit
limit_req zone=general burst=20 nodelay;
limit_conn conn_limit 10;
# Login endpoint with stricter limits
location = /login {
limit_req zone=login burst=5 nodelay;
}
# API with higher limits
location /api/ {
limit_req zone=api burst=50 nodelay;
}
}
}
Buffer Overflow Protection
Configure buffer sizes to prevent overflow attacks:
http {
# Buffer size for POST submissions
client_body_buffer_size 128k;
client_max_body_size 10m;
# Buffer size for request headers
client_header_buffer_size 1k;
large_client_header_buffers 4 16k;
# Connection settings
client_body_timeout 10s;
client_header_timeout 10s;
send_timeout 10s;
# Keep-alive settings
keepalive_timeout 5s 5s;
keepalive_requests 100;
}
User and Group Configuration
Run Nginx with minimal privileges:
# Edit main configuration
sudo nano /etc/nginx/nginx.conf
# Run as non-privileged user
user www-data;
# Worker processes (set to auto or number of CPU cores)
worker_processes auto;
# Worker connections
events {
worker_connections 1024;
}
Set proper file permissions:
# Set ownership
sudo chown -R www-data:www-data /var/www/html
# Set directory permissions
sudo find /var/www/html -type d -exec chmod 755 {} \;
# Set file permissions
sudo find /var/www/html -type f -exec chmod 644 {} \;
Common Security Measures
Implement Fail2Ban
Protect against brute force attacks:
# Install Fail2Ban
sudo apt install fail2ban -y
# Create local configuration
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
destemail = [email protected]
sendername = Fail2Ban
action = %(action_mwl)s
# Apache jail
[apache-auth]
enabled = true
port = http,https
logpath = /var/log/apache*/*error.log
[apache-badbots]
enabled = true
port = http,https
logpath = /var/log/apache*/*access.log
[apache-noscript]
enabled = true
port = http,https
logpath = /var/log/apache*/*error.log
# Nginx jail
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/*error.log
[nginx-limit-req]
enabled = true
port = http,https
logpath = /var/log/nginx/*error.log
[nginx-botsearch]
enabled = true
port = http,https
logpath = /var/log/nginx/*access.log
maxretry = 2
# Restart Fail2Ban
sudo systemctl restart fail2ban
# Check status
sudo fail2ban-client status
Keep Software Updated
Regularly update web server software:
# Ubuntu/Debian
sudo apt update
sudo apt upgrade apache2 nginx -y
# CentOS/Rocky Linux
sudo dnf update httpd nginx -y
# Enable automatic security updates
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades
Disable Unnecessary Services
Remove or disable unused services:
# List running services
systemctl list-units --type=service --state=running
# Disable unnecessary services (examples)
sudo systemctl stop telnet.socket
sudo systemctl disable telnet.socket
# Only run required web server
# If running Nginx, stop Apache and vice versa
SSL/TLS Hardening
Generate Strong DH Parameters
# Generate 4096-bit DH parameters (takes several minutes)
sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
Apache SSL Configuration
sudo nano /etc/apache2/mods-available/ssl.conf
<IfModule mod_ssl.c>
# Enable SSL
SSLEngine on
# Modern SSL protocols only
SSLProtocol -all +TLSv1.2 +TLSv1.3
# Strong cipher suites
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
# SSL compression (disable to prevent CRIME attack)
SSLCompression off
# SSL session cache
SSLSessionCache shmcb:/var/cache/apache2/ssl_scache(512000)
SSLSessionCacheTimeout 300
# Disable SSL session tickets
SSLSessionTickets off
# OCSP Stapling
SSLUseStapling on
SSLStaplingCache shmcb:/var/cache/apache2/stapling_cache(128000)
# DH parameters
SSLOpenSSLConfCmd DHParameters /etc/ssl/certs/dhparam.pem
</IfModule>
Nginx SSL Configuration
Create SSL configuration snippet:
sudo nano /etc/nginx/snippets/ssl-params.conf
# SSL certificates
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
# SSL protocols
ssl_protocols TLSv1.2 TLSv1.3;
# SSL ciphers
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
# SSL session cache
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
# DH parameters
ssl_dhparam /etc/ssl/certs/dhparam.pem;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
Access Control
IP-Based Access Control
Apache:
<Directory /var/www/html/admin>
<RequireAll>
Require ip 192.168.1.0/24
Require ip 10.0.0.100
</RequireAll>
</Directory>
Nginx:
location /admin {
allow 192.168.1.0/24;
allow 10.0.0.100;
deny all;
}
Basic Authentication
Apache:
# Create password file
sudo htpasswd -c /etc/apache2/.htpasswd admin
# Configure protection
sudo nano /etc/apache2/sites-available/example.conf
<Directory /var/www/html/admin>
AuthType Basic
AuthName "Admin Area"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user
</Directory>
Nginx:
# Create password file
sudo htpasswd -c /etc/nginx/.htpasswd admin
# Configure protection
sudo nano /etc/nginx/sites-available/example
location /admin {
auth_basic "Admin Area";
auth_basic_user_file /etc/nginx/.htpasswd;
}
Security Headers
Apache Security Headers
sudo nano /etc/apache2/conf-available/security-headers.conf
<IfModule mod_headers.c>
# HTTP Strict Transport Security
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Prevent clickjacking
Header always set X-Frame-Options "SAMEORIGIN"
# Prevent MIME sniffing
Header always set X-Content-Type-Options "nosniff"
# XSS Protection
Header always set X-XSS-Protection "1; mode=block"
# Referrer Policy
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Content Security Policy
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
# Permissions Policy
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# Remove server information
Header unset Server
Header unset X-Powered-By
</IfModule>
sudo a2enconf security-headers
sudo systemctl reload apache2
Nginx Security Headers
sudo nano /etc/nginx/snippets/security-headers.conf
# HTTP Strict Transport Security
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Prevent clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;
# Prevent MIME sniffing
add_header X-Content-Type-Options "nosniff" always;
# XSS Protection
add_header X-XSS-Protection "1; mode=block" always;
# Referrer Policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Content Security Policy
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;
# Permissions Policy
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
Include in server block:
server {
include /etc/nginx/snippets/security-headers.conf;
}
Verification and Testing
Check SSL Configuration
# Test with SSL Labs
# https://www.ssllabs.com/ssltest/
# Check locally with openssl
openssl s_client -connect example.com:443 -tls1_2
# Check certificates
openssl x509 -in /etc/ssl/certs/example.com.crt -text -noout
Security Headers Validation
# Check headers with curl
curl -I https://example.com
# Or use online tools:
# Security Headers: https://securityheaders.com
# Mozilla Observatory: https://observatory.mozilla.org
Vulnerability Scanning
# Install Nikto web scanner
sudo apt install nikto -y
# Scan web server
nikto -h https://example.com
# Install and run nmap
sudo apt install nmap -y
nmap -sV -p 80,443 example.com
Troubleshooting
403 Forbidden Errors
Cause: Overly restrictive permissions or access controls
Solution:
# Check file permissions
ls -la /var/www/html
# Fix permissions
sudo chown -R www-data:www-data /var/www/html
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;
# Check Apache/Nginx error logs
sudo tail -f /var/log/apache2/error.log
sudo tail -f /var/log/nginx/error.log
SSL Certificate Issues
Solution:
# Check certificate validity
openssl x509 -in /etc/ssl/certs/example.com.crt -noout -dates
# Verify certificate chain
openssl verify -CAfile /etc/ssl/certs/chain.pem /etc/ssl/certs/example.com.crt
# Check SSL configuration
apache2ctl configtest
nginx -t
Rate Limiting Too Aggressive
Solution:
# Adjust Nginx rate limits
limit_req zone=general burst=50 nodelay; # Increase burst
# Or increase rate
limit_req_zone $binary_remote_addr zone=general:10m rate=20r/s;
Best Practices
- Defense in Depth: Implement multiple layers of security
- Principle of Least Privilege: Grant minimum necessary permissions
- Regular Updates: Keep software current with security patches
- Strong SSL/TLS: Use TLS 1.2+ and strong ciphers
- Monitor Logs: Regularly review access and error logs
- Automated Scanning: Implement regular vulnerability scanning
- Backup Configurations: Maintain backups before changes
- Test Changes: Verify security measures don't break functionality
- Documentation: Document all security configurations
- Stay Informed: Follow security advisories and best practices
Conclusion
Web server hardening is essential for protecting your infrastructure from cyber threats. By implementing the security measures outlined in this guide—including hiding server information, configuring strong SSL/TLS, implementing access controls, adding security headers, and using Web Application Firewalls—you significantly reduce your attack surface and improve your overall security posture.
Key takeaways:
- Hide Information: Prevent information disclosure about server version and configuration
- Strong SSL/TLS: Use modern protocols and strong cipher suites
- Access Control: Implement IP-based and authentication-based restrictions
- Security Headers: Add headers to prevent common web attacks
- Rate Limiting: Protect against brute force and DDoS attacks
- Regular Updates: Keep software current with security patches
- Monitor and Test: Continuously monitor logs and test configurations
Security is an ongoing process, not a one-time task. Regularly review and update your security configurations, stay informed about new vulnerabilities, and implement defense-in-depth strategies to protect your web infrastructure effectively.


