Apache httpd: Virtual Hosts, SSL/TLS, and URL Rewriting in Production
Apache Still Powers a Quarter of the Web
Despite Nginx's rise, Apache httpd remains the most deployed web server worldwide. Its module system, .htaccess flexibility, and deep integration with cPanel-based hosting keep it dominant. For production deployments — particularly PHP applications, WordPress, and legacy apps — understanding Apache's configuration model is essential.
This guide covers the production fundamentals: virtual host configuration, SSL/TLS setup, URL rewriting, and MPM tuning.
Installation
# RHEL/Rocky Linux/AlmaLinux
sudo dnf install httpd mod_ssl -y
sudo systemctl enable --now httpd
# Ubuntu/Debian
sudo apt install apache2 -y
sudo systemctl enable --now apache2
Key paths:
- RHEL family: config at
/etc/httpd/conf/httpd.conf, vhosts in/etc/httpd/conf.d/ - Debian family: config at
/etc/apache2/, vhosts in/etc/apache2/sites-available/
Virtual Hosts
Apache uses name-based virtual hosting to serve multiple sites from one IP.
Basic Virtual Host (Debian/Ubuntu)
Create /etc/apache2/sites-available/example.com.conf:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public
<Directory /var/www/example.com/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>
Enable the site:
sudo a2ensite example.com.conf
sudo apache2ctl configtest # verify syntax
sudo systemctl reload apache2
On RHEL/Rocky (conf.d approach)
Create /etc/httpd/conf.d/example.com.conf with the same VirtualHost block. No a2ensite needed — all .conf files in conf.d/ are loaded automatically.
SSL/TLS with Let's Encrypt
# Ubuntu
sudo apt install certbot python3-certbot-apache -y
# RHEL/Rocky
sudo dnf install certbot python3-certbot-apache -y
# Obtain certificate (also auto-configures Apache)
sudo certbot --apache -d example.com -d www.example.com
# Verify auto-renewal
sudo certbot renew --dry-run
Certbot creates an SSL virtual host automatically. The result:
<VirtualHost *:443>
ServerName example.com
ServerAlias www.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
Include /etc/letsencrypt/options-ssl-apache.conf
<Directory /var/www/example.com/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com-ssl-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-ssl-access.log combined
</VirtualHost>
Manual SSL Configuration
If not using Certbot:
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/example.com.crt
SSLCertificateKeyFile /etc/ssl/private/example.com.key
SSLCertificateChainFile /etc/ssl/certs/example.com-chain.crt
# Modern TLS — disable old protocols
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
SSLSessionTickets off
# HSTS
Header always set Strict-Transport-Security "max-age=63072000"
</VirtualHost>
HTTP to HTTPS Redirect
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
# Redirect all HTTP traffic to HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>
Or using a simpler redirect directive:
<VirtualHost *:80>
ServerName example.com
Redirect permanent / https://example.com/
</VirtualHost>
mod_rewrite: URL Rewriting
Enable the module:
# Ubuntu
sudo a2enmod rewrite && sudo systemctl reload apache2
# RHEL (mod_rewrite is loaded by default)
grep -r "rewrite" /etc/httpd/conf.modules.d/
Common Rewrite Patterns
Remove www:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]
Trailing slash normalization:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [R=301,L]
Front controller (for PHP frameworks like Laravel):
<Directory /var/www/app/public>
Options -Indexes
AllowOverride All
Require all granted
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]
</Directory>
MPM: Multi-Processing Modules
Apache's MPM determines how it handles concurrent requests. The three options:
| MPM | Description | Use Case |
|---|---|---|
prefork | One process per request | PHP with mod_php (legacy) |
worker | Thread-based | High concurrency, non-PHP |
event | Async + threads | Modern — best for most workloads |
Check active MPM:
apachectl -V | grep MPM
# Or:
apache2ctl -M | grep mpm
Switch MPM (Ubuntu):
# Disable prefork, enable event
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo systemctl restart apache2
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 1000
</IfModule>
Security Headers
Add to your VirtualHost or a global conf:
<IfModule mod_headers.c>
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"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
# Remove server version info
ServerTokens Prod
ServerSignature Off
</IfModule>
Disable directory listing globally in httpd.conf / apache2.conf:
<Directory />
Options -Indexes
</Directory>
Useful Commands
# Test config syntax
sudo apachectl configtest
sudo apache2ctl -t
# List loaded modules
apachectl -M
# Enable/disable modules (Debian)
sudo a2enmod headers rewrite ssl
sudo a2dismod status
# View access log in real time
sudo tail -f /var/log/apache2/access.log
# Check which config file is active for a vhost
apache2ctl -S
# Graceful restart (zero-downtime reload)
sudo apachectl graceful
Apache's configuration is verbose compared to Nginx, but the flexibility of .htaccess and the module ecosystem make it irreplaceable for specific workloads — particularly shared hosting, WordPress, and PHP applications.
Was this article helpful?
Network & Traffic Engineer
Packets don't lie. I design and troubleshoot the network layer that everything else depends on — Nginx, Envoy, HAProxy, DNS, CDNs, and everything in between. If it touches a socket, it's my problem.
Related Articles
Apache mod_proxy: Reverse Proxy, Load Balancing, and WebSocket Support
How to use Apache httpd as a reverse proxy with mod_proxy — proxying to backend services, load balancing across multiple upstreams, WebSocket proxying, and health check configuration.
HAProxy Advanced: SSL Termination, SNI Routing, and mTLS
Advanced HAProxy SSL configuration — multi-domain SNI routing from a single frontend, mutual TLS (mTLS) for service-to-service authentication, certificate management, and OCSP stapling.
HAProxy Configuration Guide: TCP/HTTP Load Balancing, Health Checks, and SSL
A comprehensive HAProxy configuration guide covering frontends, backends, balance algorithms, active health checks, SSL termination, ACLs, rate limiting, and the runtime socket API.
Keepalived and VRRP: Building High-Availability Failover for Linux Services
How to configure Keepalived for automatic failover using VRRP — setting up master/backup pairs, virtual IPs, health-check scripts, and combining it with HAProxy or Nginx for zero-downtime load balancer HA.
Apache as a Reverse Proxy: mod_proxy Configuration Guide
Configure Apache as a reverse proxy using mod_proxy to route traffic to backend applications with load balancing and health checks.
Nginx Reverse Proxy & Caching: The Complete Guide
Set up Nginx as a reverse proxy with SSL termination, response caching, rate limiting, and load balancing for high-traffic applications.
More in Nginx
View all →Envoy Proxy: Architecture, xDS Configuration, and Getting Started
An introduction to Envoy Proxy's architecture — listeners, clusters, filters, and the xDS dynamic configuration API. Covers static configuration for standalone use and how Envoy fits into service meshes like Istio.
Envoy Traffic Management: Retries, Timeouts, Canary Deployments, and Rate Limiting
Advanced Envoy traffic management — configuring retries with exponential backoff, per-request timeouts, weighted canary routing, global rate limiting, and fault injection for resilience testing.
HAProxy + Keepalived: Production HA Load Balancer Setup
Step-by-step guide to building a highly available load balancer pair with HAProxy and Keepalived — covering the full stack from VIP configuration to health checks, stats, and SSL termination.
Fix Nginx 502 Bad Gateway Behind a Reverse Proxy
Diagnose and fix Nginx 502 Bad Gateway errors when proxying to upstream backends — check sockets, timeouts, and upstream health.