Apache: Performance, Security & .htaccess
.htaccess
.htaccess files provide per-directory configuration without restarting Apache. They're processed on every request, which has a performance cost. Require AllowOverride All in the parent Directory block. Avoid in high-traffic production if possible — move rules to the VirtualHost block instead.
# .htaccess — common recipes
# 1. Deny access to sensitive files
<FilesMatch ".(env|git|log|sql|bak|sh)$">
Require all denied
</FilesMatch>
# 2. SPA routing — serve index.html for unknown paths
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [L]
# 3. Force HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# 4. Block hotlinking
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www.)?myapp.com/ [NC]
RewriteRule .(jpg|jpeg|png|gif|webp)$ - [F,NC,L]
# 5. Custom error pages
ErrorDocument 404 /404.html
ErrorDocument 500 /500.htmlCompression (mod_deflate)
# Enable gzip compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
AddOutputFilterByType DEFLATE application/javascript application/json
AddOutputFilterByType DEFLATE application/xml application/xhtml+xml
AddOutputFilterByType DEFLATE image/svg+xml
# Don't compress already-compressed formats
SetEnvIfNoCase Request_URI .(?:gif|jpe?g|png|webp|woff2?|gz|zip)$ no-gzip
# Add Vary header
Header append Vary Accept-Encoding
</IfModule>Browser Caching (mod_expires)
<IfModule mod_expires.c>
ExpiresActive On
# Images — 1 year
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
# Fonts — 1 year
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType application/font-woff2 "access plus 1 year"
# CSS and JS — 1 month (use content hash in filename for cache-busting)
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
# HTML — no cache
ExpiresByType text/html "access plus 0 seconds"
</IfModule>
<IfModule mod_headers.c>
# Add Cache-Control header for immutable hashed assets
<FilesMatch ".[0-9a-f]{8}.(css|js)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
</IfModule>Security Hardening
# Hide Apache version and OS info
ServerTokens Prod
ServerSignature Off
# Disable TRACE method (XST attack prevention)
TraceEnable Off
# Clickjacking protection
Header always set X-Frame-Options "SAMEORIGIN"
# MIME type sniffing prevention
Header always set X-Content-Type-Options "nosniff"
# XSS Protection (legacy, but still useful)
Header always set X-XSS-Protection "1; mode=block"
# CSP (adjust for your app)
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
# Restrict access by IP
<Location /admin>
Require ip 10.0.0.0/8 203.0.113.0/24
</Location>
# Disable unused HTTP methods
<LimitExcept GET POST PUT PATCH DELETE OPTIONS HEAD>
Require all denied
</LimitExcept>Performance Tuning (event MPM)
# /etc/apache2/mods-enabled/mpm_event.conf
<IfModule mpm_event_module>
StartServers 2
MinSpareThreads 25
MaxSpareThreads 75
ThreadLimit 64
ThreadsPerChild 25
MaxRequestWorkers 150 # Total concurrent requests
MaxConnectionsPerChild 0 # 0 = unlimited (recycle after N requests if > 0)
</IfModule>
# Keep-Alive for persistent connections
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5Logs — Monitoring & Analysis
# Real-time error log
sudo tail -f /var/log/apache2/error.log
# Top requested URLs
awk '{print $7}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head 20
# Top 404s
awk '$9 == 404 {print $7}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head 20
# Top client IPs
awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head 10
# Slow requests (requires mod_logio or custom LogFormat with %D)
# Add to LogFormat: %D = request time in microseconds
awk '{if ($NF > 1000000) print $NF/1000000 "s", $7}' /var/log/apache2/access.log | sort -rn | head 20Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free