Level 2

Apache HTTP Server: virtual hosts, modules, and HTTPS

Maximilian B. 9 min read 11 views

Apache HTTP Server remains the web server behind a large portion of production Linux deployments, and it is the default in RHEL and many enterprise stacks. This article covers the parts that matter for professional Linux administration: MPM selection, virtual host configuration, essential modules like mod_rewrite and mod_proxy, SSL/TLS with Let's Encrypt, and performance tuning for real traffic loads. All examples target Apache 2.4 as shipped in Debian 13.3, Ubuntu 24.04, RHEL 10.1, and Fedora 43.

Apache MPM Models: Event, Worker, and Prefork

Apache HTTP Server: virtual hosts, modules, and HTTPS visual summary diagram
Visual summary of the key concepts in this guide.

Apache's Multi-Processing Module (MPM) determines how the server handles concurrent connections. Choosing the right MPM affects memory use, throughput, and module compatibility.

Apache MPM comparison diagram showing prefork (one process per connection, ~25 MB each), worker (hybrid multi-process/multi-thread), and event (async listener thread frees worker threads from keepalive connections) with memory usage comparison: prefork uses ~12.5 GB, worker ~2 GB, event ~0.8 GB for 500 concurrent connections on a 4 GB RAM server
MPM Model Use when
event Threads with async keepalive handling Default choice for most workloads. Handles high concurrency with low memory.
worker Hybrid multi-process/multi-thread Legacy deployments or when event MPM shows instability with specific modules.
prefork One process per connection, no threads Required when using mod_php (non-thread-safe). Avoid otherwise — high memory cost.

On Debian/Ubuntu, check and switch MPMs:

# Check current MPM
apachectl -V | grep MPM
# Server MPM: event

# Switch MPMs on Debian/Ubuntu
sudo a2dismod mpm_event
sudo a2enmod mpm_worker
sudo systemctl restart apache2

On RHEL/Fedora, the MPM is set in /etc/httpd/conf.modules.d/00-mpm.conf by commenting/uncommenting the relevant LoadModule line.

Production consequence: running prefork with mod_php for a site that could use php-fpm wastes memory. A server with 4 GB of RAM running prefork might handle 150 concurrent connections before swapping. The same server with event MPM proxying to php-fpm can handle 500+ because threads are much lighter than processes.

Apache Virtual Host Configuration

Name-based virtual hosts

Name-based virtual hosting is the standard approach. Apache uses the Host header to route requests to the correct VirtualHost block:

# /etc/apache2/sites-available/example.com.conf (Debian/Ubuntu)
# /etc/httpd/conf.d/example.com.conf (RHEL/Fedora)

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

On Debian/Ubuntu, enable with sudo a2ensite example.com.conf && sudo systemctl reload apache2. On RHEL, any .conf file in /etc/httpd/conf.d/ is loaded automatically.

IP-based virtual hosts

Used when you need different SSL certificates on the same port without SNI (rare today), or when regulatory requirements mandate IP-level separation:

<VirtualHost 10.0.1.10:443>
    ServerName secure.example.com
    DocumentRoot /var/www/secure
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/secure.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/secure.example.com/privkey.pem
</VirtualHost>

Debugging virtual host resolution

When requests hit the wrong virtual host, use Apache's built-in diagnostic to see how VirtualHost matching is processed:

# Show all configured virtual hosts and their matching order
apachectl -S

# Example output:
# *:80   is a NameVirtualHost
#   port 80 namevhost example.com (/etc/apache2/sites-enabled/example.com.conf:1)
#     alias www.example.com
#   port 80 namevhost other.com (/etc/apache2/sites-enabled/other.com.conf:1)

# Test a specific request to see which VHost it matches
curl -H "Host: example.com" http://localhost/ -I

The first VirtualHost listed for a given port serves as the default when no ServerName matches. If you see unexpected responses, check whether your default VHost is catching traffic intended for another site.

.htaccess vs server config

The .htaccess file lets you override directives per-directory without editing the main server config. It is convenient for shared hosting but has a performance cost: Apache checks for .htaccess files in every directory in the path for every request. In environments you control, set AllowOverride None and put all directives in the VirtualHost block. This eliminates the per-request filesystem lookups.

Essential Apache Modules for Production

mod_rewrite for URL rewriting

mod_rewrite handles URL rewriting for redirects, clean URLs, and routing. The most common pattern is redirecting HTTP to HTTPS:

Apache request processing architecture showing incoming HTTPS and HTTP requests passing through mod_ssl, mod_rewrite, mod_headers, and virtual host matching, then routing to static files, a backend application via mod_proxy, or PHP-FPM via mod_proxy_fcgi, with an overview of all essential production modules
<VirtualHost *:80>
    ServerName example.com
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</VirtualHost>

mod_proxy and mod_proxy_http for reverse proxying

mod_proxy turns Apache into a reverse proxy, commonly used to front application servers (Node.js, Python, Java):

<VirtualHost *:443>
    ServerName app.example.com
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/app.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/app.example.com/privkey.pem

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

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

For comparison, see how Nginx handles reverse proxying and load balancing with a more concise configuration syntax.

mod_ssl for TLS configuration

mod_ssl provides TLS support. Modern hardened configuration for TLS 1.3 and 1.2:

# /etc/apache2/conf-available/ssl-hardening.conf
SSLProtocol -all +TLSv1.3 +TLSv1.2
SSLCipherSuite TLSv1.3 TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
SSLCipherSuite SSL ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305
SSLHonorCipherOrder on
SSLSessionTickets off

mod_headers for security headers

Security headers protect against common web attacks and should be present on every production Apache HTTP Server:

Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
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"

These headers are part of a broader server hardening strategy. For firewall and network-level defenses that complement web server hardening, see Linux server security with nftables and firewalld.

mod_security2 as a web application firewall

mod_security2 is a web application firewall (WAF) module. On Debian/Ubuntu:

sudo apt install libapache2-mod-security2
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
# Edit modsecurity.conf: change SecRuleEngine DetectionOnly to SecRuleEngine On
sudo systemctl restart apache2

For meaningful protection, install the OWASP Core Rule Set (CRS). Running in DetectionOnly mode first is wise — review the audit log before switching to blocking mode to avoid false positives breaking legitimate traffic.

Apache SSL/TLS with Let's Encrypt Certificates

Certbot automates certificate issuance and renewal for HTTPS on Apache:

# Install certbot
sudo apt install certbot python3-certbot-apache   # Debian/Ubuntu
sudo dnf install certbot python3-certbot-apache   # RHEL/Fedora

# Obtain and configure certificate automatically
sudo certbot --apache -d example.com -d www.example.com

# Verify auto-renewal
sudo certbot renew --dry-run

# Renewal runs via systemd timer
systemctl list-timers | grep certbot

OCSP stapling for faster TLS handshakes

OCSP stapling lets Apache fetch the certificate's revocation status and include it in the TLS handshake, so the client does not need to contact the CA separately:

SSLUseStapling on
SSLStaplingCache shmcb:/var/run/apache2/ssl_stapling(128000)
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off

Place these directives in your global SSL config, not inside individual VirtualHost blocks. Verify with:

openssl s_client -connect example.com:443 -status < /dev/null 2>&1 | grep -A 3 "OCSP Response"

Apache Logging and Access Control

Apache provides two log types per VirtualHost:

  • ErrorLog: server errors, module warnings, application failures.
  • CustomLog: access records in configurable format.
# Custom log format with response time (microseconds)
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %D" detailed
CustomLog ${APACHE_LOG_DIR}/example.com-access.log detailed

The %D field (response time in microseconds) is valuable for spotting slow requests. For JSON-format logs compatible with log aggregators like the ELK stack:

LogFormat "{\"time\":\"%{%Y-%m-%dT%H:%M:%S}t\",\"remote\":\"%a\",\"method\":\"%m\",\"uri\":\"%U\",\"status\":%>s,\"bytes\":%O,\"duration_us\":%D}" json
CustomLog ${APACHE_LOG_DIR}/example.com-access.json json

Access control with Require directives

Apache 2.4 uses Require directives (mod_authz_core) instead of the deprecated Order/Allow/Deny:

# Allow only internal network
<Directory /var/www/internal>
    Require ip 10.0.0.0/8 192.168.0.0/16
</Directory>

# Require authentication
<Directory /var/www/admin>
    AuthType Basic
    AuthName "Admin Area"
    AuthUserFile /etc/apache2/.htpasswd
    Require valid-user
</Directory>

For sensitive admin areas, consider combining IP restrictions with SSH tunneling for an additional security layer. Learn how to set up secure tunnels in OpenSSH advanced tunneling and hardening.

Apache Performance Tuning for Production

Key tuning parameters for the event MPM under load:

<IfModule mpm_event_module>
    StartServers             4
    MinSpareThreads         75
    MaxSpareThreads        250
    ThreadLimit             64
    ThreadsPerChild         25
    MaxRequestWorkers      400
    MaxConnectionsPerChild   0
</IfModule>

# KeepAlive settings
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5

The MaxRequestWorkers setting is your hard ceiling for concurrent connections. Calculate it based on available RAM: if each worker thread uses about 10 MB and you have 4 GB available for Apache, set it to roughly 400. Going higher leads to swapping, which is worse than queuing.

KeepAliveTimeout 5 (seconds) is a good balance. Too high (like the old default of 15) wastes threads on idle connections. Too low forces clients to renegotiate connections constantly.

Monitoring Apache under load

Enable mod_status to get real-time server metrics for tuning decisions:

<Location "/server-status">
    SetHandler server-status
    Require ip 10.0.0.0/8 127.0.0.1
</Location>

# Enable extended status for detailed per-request info
ExtendedStatus On

Access http://yourserver/server-status?auto for machine-readable output that monitoring tools like Prometheus (with the Apache exporter) can scrape. Watch for the scoreboard showing workers in "W" (writing) state — a high count indicates slow backend responses or undersized worker pools.

Apache HTTP Server Quick Reference

Task Command
Check config syntax apachectl configtest
Show loaded modules apachectl -M
Show active MPM apachectl -V | grep MPM
Enable site (Debian) sudo a2ensite example.com.conf
Enable module (Debian) sudo a2enmod rewrite
Obtain Let's Encrypt cert sudo certbot --apache -d example.com
Test renewal sudo certbot renew --dry-run
Check OCSP stapling openssl s_client -connect host:443 -status
Test TLS config openssl s_client -connect host:443 -tls1_3
Graceful restart apachectl graceful
Create htpasswd file htpasswd -c /etc/apache2/.htpasswd admin
Show active connections ss -tlnp | grep apache2
Redirect HTTP to HTTPS RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Summary

Apache 2.4 on modern Linux distributions defaults to the event MPM, which handles high concurrency well when properly tuned. Name-based virtual hosts cover most hosting scenarios. Use mod_proxy to front application backends, mod_rewrite for URL routing, and mod_ssl with Let's Encrypt for automated TLS certificates. Set AllowOverride None in environments you control to avoid the performance cost of .htaccess lookups. Harden your TLS configuration to TLS 1.2+ with strong ciphers, enable HSTS and OCSP stapling, and consider mod_security2 with the OWASP CRS for WAF protection. Size MaxRequestWorkers based on actual memory, not guesswork, and keep KeepAliveTimeout low. These settings, tested on Debian 13.3, Ubuntu 24.04, RHEL 10.1, and Fedora 43, will give you a solid production baseline. For high-traffic sites requiring advanced load balancing, consider pairing Apache with an Nginx reverse proxy front end.

Share this article
X / Twitter LinkedIn Reddit