DevOpsil
Nginx
89%
Needs Review

Apache httpd: Virtual Hosts, SSL/TLS, and URL Rewriting in Production

Muhammad HassanMuhammad Hassan4 min read

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:

MPMDescriptionUse Case
preforkOne process per requestPHP with mod_php (legacy)
workerThread-basedHigh concurrency, non-PHP
eventAsync + threadsModern — 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.

Share:

Was this article helpful?

Muhammad Hassan
Muhammad Hassan

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

More in Nginx

View all →

Discussion