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

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

  1. Defense in Depth: Implement multiple layers of security
  2. Principle of Least Privilege: Grant minimum necessary permissions
  3. Regular Updates: Keep software current with security patches
  4. Strong SSL/TLS: Use TLS 1.2+ and strong ciphers
  5. Monitor Logs: Regularly review access and error logs
  6. Automated Scanning: Implement regular vulnerability scanning
  7. Backup Configurations: Maintain backups before changes
  8. Test Changes: Verify security measures don't break functionality
  9. Documentation: Document all security configurations
  10. 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.