Vucense

Apache Security Hardening Guide 2026: Headers, mod_security & Rate Limiting

🟡Intermediate

Harden Apache HTTP Server against common attacks on Ubuntu 24.04. Covers mod_security WAF, ServerTokens config, directory traversal blocking, mod_evasive rate limiting, and security headers.

Divya Prakash

Author

Divya Prakash

AI Systems Architect & Founder

Published

Duration

Reading

15 min

Build

20 min

Apache Security Hardening Guide 2026: Headers, mod_security & Rate Limiting
Article Roadmap

Key Takeaways

  • ServerTokens Prod immediately: Hides Apache version from all HTTP responses — one-line change. Compare with Caddy Reverse Proxy for a modern security-first alternative.
  • ModSecurity + CRS: Free WAF that blocks OWASP Top 10 attacks. Install in detection mode first, then enforcement. Combine with Apache SSL/HTTPS for complete security.
  • Options -Indexes: Never allow directory browsing in production.
  • Security headers: HSTS, X-Frame-Options, X-Content-Type-Options — same headers as Nginx, same impact. See Let’s Encrypt SSL Guide to enable HTTPS with hardened headers.

Introduction

Direct Answer: How do I harden Apache on Ubuntu 24.04 in 2026?

Six steps: (1) ServerTokens Prod and ServerSignature Off in /etc/apache2/apache2.conf — hides version; (2) Options -Indexes FollowSymLinks in default virtual host — prevents directory listing; (3) sudo apt-get install libapache2-mod-security2 && sudo a2enmod security2 — install ModSecurity WAF; (4) Configure security headers with Header always set Strict-Transport-Security "max-age=31536000", Header always set X-Frame-Options SAMEORIGIN, and Header always set X-Content-Type-Options nosniff using mod_headers; (5) sudo apt-get install libapache2-mod-evasive && sudo a2enmod evasive — install rate limiting; (6) Block common attack paths with <Location "/.git"> Require all denied </Location>. Test with apache2ctl configtest after each change.


Part 1: Hide Server Information

# Edit main Apache config
sudo tee -a /etc/apache2/conf-available/security-tokens.conf << 'EOF'
# Hide Apache version and OS from HTTP headers and error pages
ServerTokens Prod        # Returns "Server: Apache" instead of "Server: Apache/2.4.58 (Ubuntu)"
ServerSignature Off      # Remove Apache version from error pages

# Disable TRACE method (potential information disclosure)
TraceEnable Off
EOF

sudo a2enconf security-tokens
sudo apache2ctl configtest && sudo systemctl reload apache2

# Verify
curl -sI http://localhost | grep "^Server:"

Expected output (before → after):

Before: Server: Apache/2.4.58 (Ubuntu)
After:  Server: Apache

Part 2: Security Headers

sudo a2enmod headers

sudo tee /etc/apache2/conf-available/security-headers.conf << 'EOF'
<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" env=HTTPS
    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 unset X-Powered-By     # Remove PHP version if present

    # CSP — start in report-only mode
    Header always set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
</IfModule>
EOF

sudo a2enconf security-headers
sudo apache2ctl configtest && sudo systemctl reload apache2

# Verify headers
curl -sI https://yourdomain.com | grep -E "Strict|X-Frame|X-Content|Referrer|Permissions"

Part 3: Directory and File Protection

sudo tee /etc/apache2/conf-available/directory-security.conf << 'EOF'
# Disable directory browsing globally
<Directory /var/www/>
    Options -Indexes +FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

# Block access to sensitive files and directories
<DirectoryMatch "(\.git|\.svn|\.env|\.htaccess|\.htpasswd)">
    Require all denied
</DirectoryMatch>

<FilesMatch "(\.git|\.env|composer\.(json|lock)|package\.(json|lock))$">
    Require all denied
</FilesMatch>

# Prevent access to backup files
<FilesMatch "\.(bak|backup|old|orig|save|swp|tmp)$">
    Require all denied
</FilesMatch>
EOF

sudo a2enconf directory-security
sudo apache2ctl configtest && sudo systemctl reload apache2

# Test: blocked paths should return 403
curl -sI http://localhost/.git/config | grep "^HTTP"
curl -sI http://localhost/.env | grep "^HTTP"

Expected output:

HTTP/1.1 403 Forbidden
HTTP/1.1 403 Forbidden

Part 4: ModSecurity WAF

# Install ModSecurity
sudo apt-get install -y libapache2-mod-security2
sudo a2enmod security2

# Use the recommended configuration
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf

# Set to DetectionOnly first (log, don't block) — switch to On after tuning
sudo sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine DetectionOnly/' /etc/modsecurity/modsecurity.conf

# Install OWASP Core Rule Set
sudo apt-get install -y modsecurity-crs

# Enable CRS
sudo tee /etc/apache2/conf-available/modsecurity-crs.conf << 'EOF'
Include /etc/modsecurity/crs/crs-setup.conf
Include /etc/modsecurity/crs/rules/*.conf
EOF

sudo a2enconf modsecurity-crs
sudo apache2ctl configtest && sudo systemctl reload apache2

# Test: attempt SQL injection — should be logged
curl "http://localhost/?id=1' OR '1'='1"
sudo tail -5 /var/log/apache2/modsec_audit.log

Expected output (detection mode — logged but not blocked):

--[audit log entry]--
Message: Warning. detected SQLi using libinjection with fingerprint 's&1'
Severity: CRITICAL
File: /etc/modsecurity/crs/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf
# Switch to enforcement mode after tuning false positives
sudo sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/modsecurity/modsecurity.conf
sudo systemctl reload apache2

Part 5: Rate Limiting with mod_evasive

sudo apt-get install -y libapache2-mod-evasive
sudo a2enmod evasive

sudo tee /etc/apache2/mods-available/evasive.conf << 'EOF'
<IfModule mod_evasive20.c>
    DOSHashTableSize    3097
    DOSPageCount        5       # Max requests to same page in DOSPageInterval
    DOSSiteCount        50      # Max requests to entire site in DOSSiteInterval
    DOSPageInterval     1       # Seconds
    DOSSiteInterval     1       # Seconds
    DOSBlockingPeriod   600     # Block for 600 seconds (10 minutes)

    DOSLogDir           /var/log/apache2/evasive/
    # DOSEmailNotify      [email protected]
</IfModule>
EOF

sudo mkdir -p /var/log/apache2/evasive
sudo chown www-data /var/log/apache2/evasive
sudo apache2ctl configtest && sudo systemctl reload apache2

Part 6: Security Audit

echo "=== APACHE SECURITY AUDIT ==="

echo "[ Server token ]"
curl -sI http://localhost | grep "^Server:" | sed 's/^/  /'

echo ""
echo "[ Security headers ]"
curl -sI https://yourdomain.com 2>/dev/null | \
  grep -iE "strict-transport|x-frame|x-content" | \
  awk '{print "  ✓", $0}'

echo ""
echo "[ ModSecurity enabled ]"
apache2ctl -M 2>/dev/null | grep security2_module | \
  awk '{print "  ✓ ModSecurity active"}'

echo ""
echo "[ Directory listing disabled ]"
code=$(curl -sI -o/dev/null -w "%{http_code}" http://localhost/)
[ "$code" != "200" ] || echo "  Check: request to / returned 200 — verify Options -Indexes"
curl -sI http://localhost/.git/config 2>/dev/null | \
  grep "^HTTP" | grep -q "403" && echo "  ✓ .git access blocked" || echo "  ✗ .git access NOT blocked"

Conclusion

Apache is now hardened: version disclosure eliminated, security headers blocking browser-based attacks, directory browsing disabled, ModSecurity WAF blocking OWASP Top 10 attacks, and mod_evasive rate-limiting abusive IPs. Run the audit script after every configuration change.

See How to Enable HTTPS on Apache with Let’s Encrypt for SSL, and UFW Firewall Tutorial 2026 for the network perimeter.


People Also Ask

How is ModSecurity different from a firewall like UFW?

UFW operates at the network layer — it allows or blocks connections based on IP addresses and ports, before HTTP traffic is examined. ModSecurity operates at the application layer — it inspects the content of HTTP requests (URLs, headers, body) for attack patterns after the connection is established. UFW blocks port scanning and connection-level attacks; ModSecurity blocks SQL injection, XSS, and application-level attacks. You need both: UFW for network perimeter, ModSecurity for application-level protection.



Further Reading

Vucense Guides

Official Documentation & Tools

Security Tools

Tested on: Ubuntu 24.04 LTS (Hetzner CX22). Apache 2.4.58, ModSecurity 2.9.7, OWASP CRS 3.3.5. Last verified: May 16, 2026.

Further Reading

All Dev Corner

Comments