All topics
General · Learning hub

Apache notes for developers

Master Apache with a curated set of 7 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore General notes
Apache

Apache Configuration Basics

Apache Configuration Basics Apache HTTP Server is the most widely deployed web server in the world. Its configuration is file-driven — understanding the httpd.c

Apache Configuration Basics

Apache HTTP Server is the most widely deployed web server in the world. Its configuration is file-driven — understanding the httpd.conf structure, MPM selection, and service management is the foundation of running Apache in production.

httpd.conf Structure & Core Directives

The main config file is typically at /etc/apache2/apache2.conf (Debian/Ubuntu) or /etc/httpd/conf/httpd.conf (RHEL/CentOS). It uses a directive-based syntax inside block containers.

# Core server identity directives
ServerName www.example.com
ServerAlias example.com
ServerAdmin webmaster@example.com

# Document root — where your site files live
DocumentRoot "/var/www/html"

# Directory block — controls access to a path
<Directory "/var/www/html">
    Options Indexes FollowSymLinks
    AllowOverride All          # Allow .htaccess to override directives
    Require all granted        # Apache 2.4 access control (replaces Order/Allow/Deny)
</Directory>

# Restrict access to a sensitive directory
<Directory "/var/www/html/admin">
    Require ip 192.168.1.0/24  # Only allow internal network
</Directory>

# Options values:
#   Indexes        — show directory listing if no index file exists
#   FollowSymLinks — follow symbolic links
#   ExecCGI        — allow CGI scripts
#   None           — disable all options
#   -Indexes       — disable Indexes specifically

# Listen on multiple ports
Listen 80
Listen 443

# Include additional config files
Include conf.d/*.conf
IncludeOptional sites-enabled/*.conf

MPM — Multi-Processing Modules

The MPM controls how Apache handles concurrent connections. Choosing the right one impacts performance, memory usage, and PHP compatibility.

# Check active MPM
apachectl -V | grep MPM
apache2ctl -V | grep "Server MPM"

# prefork  — one process per connection; required for non-thread-safe PHP (mod_php)
# worker   — threads + processes; better memory efficiency
# event    — like worker but handles keep-alive connections in a dedicated thread (best for modern workloads)

# Configure event MPM in /etc/apache2/mods-available/mpm_event.conf
<IfModule mpm_event_module>
    StartServers             2
    MinSpareThreads         25
    MaxSpareThreads         75
    ThreadLimit             64
    ThreadsPerChild         25
    MaxRequestWorkers      150
    MaxConnectionsPerChild   0
</IfModule>

# Switch MPM on Ubuntu/Debian
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo systemctl restart apache2

# On RHEL/CentOS edit /etc/httpd/conf.modules.d/00-mpm.conf
# Comment out LoadModule mpm_prefork_module and uncomment mpm_event_module

Service Management

# systemctl (modern — use on all systemd distros)
sudo systemctl start apache2        # Start
sudo systemctl stop apache2         # Stop
sudo systemctl restart apache2      # Full restart (drops connections)
sudo systemctl reload apache2       # Graceful reload (no dropped connections)
sudo systemctl enable apache2       # Auto-start on boot
sudo systemctl status apache2       # Show status + recent logs

# apachectl / apache2ctl (cross-platform alternative)
sudo apachectl start
sudo apachectl graceful             # Graceful restart (waits for in-flight requests)
sudo apachectl configtest           # Validate config syntax BEFORE reloading
sudo apachectl -t                   # Same as configtest
sudo apachectl -S                   # Show parsed virtual host config

# RHEL/CentOS uses httpd instead of apache2
sudo systemctl restart httpd
sudo apachectl configtest

# Log files
tail -f /var/log/apache2/access.log
tail -f /var/log/apache2/error.log
tail -f /var/log/httpd/error_log    # RHEL/CentOS path

# Custom log format
LogFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" combined
CustomLog /var/log/apache2/access.log combined
ErrorLog /var/log/apache2/error.log
LogLevel warn
Apache

Virtual Hosts & SSL

Virtual Hosts & SSL Virtual hosts let a single Apache server serve multiple domains. SSL/TLS via mod_ssl enables HTTPS. Combining both is the standard setup for

Virtual Hosts & SSL

Virtual hosts let a single Apache server serve multiple domains. SSL/TLS via mod_ssl enables HTTPS. Combining both is the standard setup for any production server.

Name-Based Virtual Hosts

# /etc/apache2/sites-available/example.com.conf

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com/public
    ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
    CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined

    <Directory /var/www/example.com/public>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

# Enable the site and reload
sudo a2ensite example.com.conf
sudo systemctl reload apache2

# Disable a site
sudo a2dissite 000-default.conf

# The default catch-all vhost (first match wins by filename order)
# Sites are loaded alphabetically — prefix with numbers to control order:
# 000-default.conf, 010-example.com.conf, 020-api.example.com.conf

# IP-based virtual host (different IPs, same server)
<VirtualHost 192.168.1.10:80>
    ServerName site-a.example.com
    DocumentRoot /var/www/site-a
</VirtualHost>

<VirtualHost 192.168.1.11:80>
    ServerName site-b.example.com
    DocumentRoot /var/www/site-b
</VirtualHost>

SSL/TLS with mod_ssl

# Enable mod_ssl (Ubuntu/Debian)
sudo a2enmod ssl
sudo systemctl restart apache2

# Obtain a free certificate via Certbot (Let's Encrypt)
sudo apt install certbot python3-certbot-apache
sudo certbot --apache -d example.com -d www.example.com
# Certbot will create and configure the SSL vhost automatically

# Manual SSL VirtualHost at port 443
<VirtualHost *:443>
    ServerName example.com
    DocumentRoot /var/www/example.com/public

    SSLEngine on
    SSLCertificateFile      /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile   /etc/letsencrypt/live/example.com/privkey.pem

    # Modern TLS settings (Mozilla Intermediate compatibility)
    SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite          ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
    SSLHonorCipherOrder     off
    SSLSessionTickets       off

    # HSTS — tell browsers to always use HTTPS
    Header always set Strict-Transport-Security "max-age=63072000"
</VirtualHost>

# HTTP → HTTPS redirect
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    RewriteEngine on
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

HTTP/2 & Certificate Renewal

# Enable HTTP/2 (requires event or worker MPM, not prefork)
sudo a2enmod http2
# Add to global config or per-vhost
Protocols h2 http/1.1

# Verify HTTP/2 is active
curl -I --http2 https://example.com
# Look for: HTTP/2 200

# Auto-renew Let's Encrypt certs (Certbot sets up a systemd timer)
sudo systemctl status certbot.timer
# Manual renew dry run
sudo certbot renew --dry-run
# Force renew
sudo certbot renew --force-renewal -d example.com

# Test SSL configuration online
# Run: openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 </dev/null 2>/dev/null | openssl x509 -noout -dates
# Shows certificate valid dates

# Check which sites are enabled
ls /etc/apache2/sites-enabled/
apachectl -S   # Full parsed virtual host summary with config file + line numbers
Apache

.htaccess & Modules

.htaccess & Apache Modules .htaccess files allow per-directory configuration without restarting Apache. Combined with modules like mod_rewrite, mod_headers, and

.htaccess & Apache Modules

.htaccess files allow per-directory configuration without restarting Apache. Combined with modules like mod_rewrite, mod_headers, and mod_proxy, they cover URL rewriting, compression, caching, and reverse proxying.

mod_rewrite — URL Rewriting

# Enable mod_rewrite
sudo a2enmod rewrite
# Ensure AllowOverride All is set in the Directory block for .htaccess to work

# .htaccess — SPA (Single Page App) routing
RewriteEngine On
RewriteBase /
RewriteRule ^index.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]

# Redirect old URL to new URL (301 permanent)
RewriteRule ^old-page.html$ /new-page/ [R=301,L]

# Redirect entire old directory
RewriteRule ^old-blog/(.*)$ /blog/$1 [R=301,L]

# Force www
RewriteCond %{HTTP_HOST} !^www.
RewriteRule ^(.*)$ https://www.%{HTTP_HOST}/$1 [R=301,L]

# Remove trailing slash (except root)
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [R=301,L]

# RewriteCond flags:
#   %{HTTP_HOST}       — the incoming Host header
#   %{REQUEST_FILENAME} — absolute filesystem path
#   !-f  — not a real file
#   !-d  — not a real directory
#   [NC] — case-insensitive match
#   [OR] — logical OR between conditions

# RewriteRule flags:
#   [L]        — last rule, stop processing
#   [R=301]    — redirect with HTTP status
#   [P]        — proxy (requires mod_proxy)
#   [QSA]      — append query string
#   [NE]       — no URL encoding of special chars

mod_headers, mod_deflate & mod_expires

# Enable modules
sudo a2enmod headers deflate expires

# mod_headers — add/modify response headers in .htaccess or vhost config
<IfModule mod_headers.c>
    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=()"
    # Remove server version from response
    Header unset Server
    Header always unset X-Powered-By
</IfModule>

# mod_deflate — gzip compression
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/plain text/css
    AddOutputFilterByType DEFLATE application/javascript application/json
    AddOutputFilterByType DEFLATE application/xml image/svg+xml font/woff2
    # Don't compress already-compressed formats
    SetEnvIfNoCase Request_URI .(?:gif|jpe?g|png|webp|zip|gz|br)$ no-gzip
</IfModule>

# mod_expires — browser cache control
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresDefault                         "access plus 1 month"
    ExpiresByType text/html                "access plus 0 seconds"
    ExpiresByType text/css                 "access plus 1 year"
    ExpiresByType application/javascript   "access plus 1 year"
    ExpiresByType image/png                "access plus 1 year"
    ExpiresByType image/jpeg               "access plus 1 year"
    ExpiresByType image/webp               "access plus 1 year"
    ExpiresByType font/woff2               "access plus 1 year"
</IfModule>

mod_proxy — Reverse Proxy

# Enable proxy modules
sudo a2enmod proxy proxy_http proxy_wstunnel

# Reverse proxy a Node.js app running on port 3000
<VirtualHost *:80>
    ServerName api.example.com

    ProxyPreserveHost On
    ProxyPass        / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/

    # WebSocket support (for Socket.io etc.)
    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} websocket [NC]
    RewriteCond %{HTTP:Connection} upgrade [NC]
    RewriteRule ^/?(.*) "ws://127.0.0.1:3000/$1" [P,L]
</VirtualHost>

# Proxy only a path prefix (e.g. /api → backend)
ProxyPass        /api/ http://127.0.0.1:4000/
ProxyPassReverse /api/ http://127.0.0.1:4000/

# Module management commands
sudo a2enmod <module>     # Enable a module
sudo a2dismod <module>    # Disable a module
apache2ctl -M             # List all loaded modules
apache2ctl -M | grep rewrite   # Check if specific module is loaded
Apache

Performance & Security

Apache Performance & Security A default Apache install is not tuned for production. Enabling caching, tuning KeepAlive, hiding server info, and adding security

Apache Performance & Security

A default Apache install is not tuned for production. Enabling caching, tuning KeepAlive, hiding server info, and adding security headers are the essential steps before going live.

KeepAlive & Connection Tuning

# In apache2.conf or vhost config

# KeepAlive — reuse TCP connections for multiple requests
KeepAlive On
MaxKeepAliveRequests 100        # Max requests per connection (0 = unlimited)
KeepAliveTimeout 5              # Seconds to wait for next request (lower = more efficient)

# Timeout settings
Timeout 60                      # Request timeout in seconds
RequestReadTimeout header=20-40,MinRate=500 body=20,MinRate=500

# Limit request sizes (protect against large body attacks)
LimitRequestBody 10485760       # 10 MB max request body

# mod_cache with mod_cache_disk
sudo a2enmod cache cache_disk
<IfModule mod_cache_disk.c>
    CacheEnable disk /
    CacheRoot /var/cache/apache2/mod_cache_disk
    CacheDirLevels 2
    CacheDirLength 1
    CacheDefaultExpire 3600     # 1 hour default
    CacheMaxExpire 86400        # 24 hour max
    CacheIgnoreHeaders Set-Cookie
</IfModule>

Security Hardening

# Hide server version and OS info
ServerTokens Prod              # Show only "Apache" not version
ServerSignature Off            # Remove version from error pages

# Disable directory listing globally
<Directory />
    Options -Indexes
    AllowOverride None
    Require all denied         # Deny everything by default, open up explicitly
</Directory>

<Directory /var/www/html>
    Options -Indexes +FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>

# Block access to hidden files (.git, .env, .htpasswd)
<FilesMatch "^.">
    Require all denied
</FilesMatch>

# Block access to sensitive extensions
<FilesMatch ".(sql|bak|config|log|sh)$">
    Require all denied
</FilesMatch>

# Basic auth with .htpasswd
sudo apt install apache2-utils
htpasswd -c /etc/apache2/.htpasswd admin    # Create file and add user
htpasswd /etc/apache2/.htpasswd editor      # Add another user

<Directory /var/www/html/private>
    AuthType Basic
    AuthName "Restricted Area"
    AuthUserFile /etc/apache2/.htpasswd
    Require valid-user
</Directory>

# mod_ratelimit — limit bandwidth per connection
sudo a2enmod ratelimit
<Location /downloads>
    SetOutputFilter RATE_LIMIT
    SetEnv rate-limit 400        # 400 KB/s per connection
</Location>

Security Headers & mod_security

# Add security headers (in vhost or .htaccess)
sudo a2enmod headers

<IfModule mod_headers.c>
    # Prevent clickjacking
    Header always set X-Frame-Options "SAMEORIGIN"
    # Prevent MIME type sniffing
    Header always set X-Content-Type-Options "nosniff"
    # XSS protection (legacy browsers)
    Header always set X-XSS-Protection "1; mode=block"
    # HSTS — force HTTPS for 2 years including subdomains
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    # Content Security Policy
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
    # Referrer policy
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>

# Install ModSecurity WAF
sudo apt install libapache2-mod-security2
sudo a2enmod security2
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
# Change DetectionOnly to On to enforce rules
sudo sed -i "s/SecRuleEngine DetectionOnly/SecRuleEngine On/" /etc/modsecurity/modsecurity.conf

# Test config and reload
sudo apachectl configtest && sudo systemctl reload apache2

# Check for misconfigurations causing 403/500 errors
sudo tail -50 /var/log/apache2/error.log
sudo journalctl -u apache2 --since "5 minutes ago"
Apache

Fundamentals & Core Configuration

Apache HTTP Server: Fundamentals & Core Configuration Apache HTTP Server is the world's most widely deployed web server. It's a module-based server that handles

Apache HTTP Server: Fundamentals & Core Configuration

Apache HTTP Server is the world's most widely deployed web server. It's a module-based server that handles everything from static file serving to proxying, SSL termination, and URL rewriting.

Apache vs Nginx

  • Apache: process/thread per connection (event MPM is more efficient), .htaccess per-directory config, rich module ecosystem, battle-tested — good for shared hosting, dynamic content via mod_php

  • Nginx: event-driven async architecture, no .htaccess (config reload needed), excellent static file serving, better at high concurrency — preferred for modern deployments

  • Practical choice: Nginx as reverse proxy → Apache as backend is a common combo

Installation

# Debian/Ubuntu
sudo apt update && sudo apt install apache2
sudo systemctl enable --now apache2

# RHEL/CentOS/Amazon Linux
sudo dnf install httpd
sudo systemctl enable --now httpd

# macOS (via Homebrew)
brew install httpd
brew services start httpd

MPM (Multi-Processing Module)

  • prefork: one process per request, no threading, compatible with non-thread-safe libs (older mod_php). High memory use.

  • worker: multi-process + multi-threaded. More concurrent connections with less memory.

  • event: like worker but keeps connections alive without tying up threads. Best for modern workloads. Default on Ubuntu.

# Check active MPM
apache2ctl -V | grep MPM
httpd -V | grep MPM

# Switch MPM (Ubuntu)
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo systemctl restart apache2

Key Configuration Files

# Ubuntu/Debian paths
/etc/apache2/apache2.conf       # Main config
/etc/apache2/ports.conf         # Listen directives
/etc/apache2/sites-available/   # Virtual host configs (enable with a2ensite)
/etc/apache2/sites-enabled/     # Symlinks to active sites
/etc/apache2/mods-available/    # Available modules
/etc/apache2/mods-enabled/      # Enabled modules (symlinks)
/var/log/apache2/access.log     # Access log
/var/log/apache2/error.log      # Error log

# RHEL/CentOS paths
/etc/httpd/conf/httpd.conf       # Main config
/etc/httpd/conf.d/               # Additional configs
/var/log/httpd/access_log
/var/log/httpd/error_log

Core Directives

# apache.conf / httpd.conf key settings

ServerName www.example.com
ServerAdmin webmaster@example.com

# Document root for default site
DocumentRoot /var/www/html

# Directory permissions
<Directory /var/www/html>
    Options -Indexes +FollowSymLinks    # Disable directory listing
    AllowOverride All                   # Allow .htaccess (use None for performance)
    Require all granted
</Directory>

# Logging
LogLevel warn
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined

# Log format definition
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

# Keep-Alive
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5

# Listen on ports
Listen 80
Listen 443

apachectl Commands

# Start / stop / restart
sudo apachectl start
sudo apachectl stop
sudo apachectl restart       # Full restart (brief downtime)
sudo apachectl graceful      # Reload config, finish active connections first
sudo apachectl graceful-stop

# Validate config before restarting
sudo apachectl configtest
sudo apachectl -t            # Same thing

# Show compiled-in settings
apache2ctl -V

# List loaded modules
apache2ctl -M

mod_status — Server Status

# Enable mod_status
LoadModule status_module modules/mod_status.so

<Location /server-status>
    SetHandler server-status
    Require ip 127.0.0.1 10.0.0.0/8    # Restrict access
</Location>

ExtendedStatus On    # Include per-request details
# Enable on Ubuntu
sudo a2enmod status
curl http://localhost/server-status?auto   # Machine-readable output
Apache

Virtual Hosts, Modules & SSL/TLS

Apache: Virtual Hosts, Modules & SSL/TLS Name-Based Virtual Hosts Virtual hosts let one server handle multiple domains. Apache uses the Host header to route req

Apache: Virtual Hosts, Modules & SSL/TLS

Name-Based Virtual Hosts

Virtual hosts let one server handle multiple domains. Apache uses the Host header to route requests to the correct VirtualHost block.

# /etc/apache2/sites-available/myapp.conf

<VirtualHost *:80>
    ServerName myapp.com
    ServerAlias www.myapp.com

    DocumentRoot /var/www/myapp
    ErrorLog ${APACHE_LOG_DIR}/myapp-error.log
    CustomLog ${APACHE_LOG_DIR}/myapp-access.log combined

    <Directory /var/www/myapp>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    # Redirect all HTTP to HTTPS
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>
# Enable site, disable default
sudo a2ensite myapp.conf
sudo a2dissite 000-default.conf
sudo apachectl graceful

HTTPS Virtual Host with SSL

<VirtualHost *:443>
    ServerName myapp.com
    ServerAlias www.myapp.com

    DocumentRoot /var/www/myapp

    SSLEngine on
    SSLCertificateFile      /etc/letsencrypt/live/myapp.com/fullchain.pem
    SSLCertificateKeyFile   /etc/letsencrypt/live/myapp.com/privkey.pem

    # Modern TLS config (Mozilla Intermediate)
    SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite          ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
    SSLHonorCipherOrder     off
    SSLSessionTickets       off

    # HSTS
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

    # Security headers
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
</VirtualHost>

Let's Encrypt with Certbot

# Install Certbot
sudo apt install certbot python3-certbot-apache

# Obtain and auto-configure SSL
sudo certbot --apache -d myapp.com -d www.myapp.com

# Standalone mode (if Apache is not running)
sudo certbot certonly --standalone -d myapp.com

# Test renewal
sudo certbot renew --dry-run

# Certbot creates systemd timer for auto-renewal
systemctl status certbot.timer

Key Modules

# Enable / disable modules
sudo a2enmod rewrite     # URL rewriting (mod_rewrite)
sudo a2enmod ssl         # HTTPS
sudo a2enmod headers     # Response headers (mod_headers)
sudo a2enmod deflate     # Gzip compression
sudo a2enmod expires     # Cache-Control headers
sudo a2enmod proxy       # Reverse proxy
sudo a2enmod proxy_http  # HTTP proxy
sudo a2dismod autoindex  # Disable directory listing
sudo apachectl graceful

mod_rewrite — URL Rewriting

<VirtualHost *:443>
    RewriteEngine On

    # HTTPS redirect
    RewriteCond %{HTTPS} off
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

    # www → non-www redirect
    RewriteCond %{HTTP_HOST} ^www.(.+)$ [NC]
    RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]

    # SPA — serve index.html for all non-file routes
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^ /index.html [L]

    # Old URL → new URL (301 permanent)
    RewriteRule ^/old-blog/(.*)$ /blog/$1 [R=301,L]

    # Block access to hidden files (.git, .env)
    RewriteRule (?:^|/). - [F,L]
</VirtualHost>

Reverse Proxy

# Proxy to Node.js app on port 3000
<VirtualHost *:443>
    ServerName api.myapp.com

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/api.myapp.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/api.myapp.com/privkey.pem

    ProxyPreserveHost On
    ProxyPass / http://localhost:3000/
    ProxyPassReverse / http://localhost:3000/

    # Forward real client IP
    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Forwarded-For %{REMOTE_ADDR}s
</VirtualHost>
Apache

Performance, Security & .htaccess

Apache: Performance, Security & .htaccess .htaccess .htaccess files provide per-directory configuration without restarting Apache. They're processed on every re

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.html

Compression (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 5

Logs — 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 20

Keep your Apache knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever