Configure Apache Web Server on Linux: Virtual Hosts and HTTPS

Apache HTTP Server is the most widely deployed web server in the world. Understanding Apache configuration — virtual hosts, access control, SSL, logging, and performance tuning — is essential for RHCA and for any Linux sysadmin supporting web applications.

How Web Servers Work

A web server is software that:

  1. Listens on a TCP port (default 80 for HTTP, 443 for HTTPS)
  2. Accepts incoming HTTP connections from clients (browsers, curl, APIs)
  3. Parses the HTTP request (method, URL, headers)
  4. Maps the URL to a file on disk or a dynamic handler (CGI, PHP, proxied app)
  5. Sends back an HTTP response (status code, headers, body)

Apache follows a request/response cycle. Each client connection creates a request, Apache processes it, sends the response, and optionally keeps the connection alive for reuse (HTTP keep-alive).

Apache Architecture — MPMs (Multi-Processing Modules)

Apache's processing model is determined by the MPM (Multi-Processing Module):

MPMModelUse CaseRHEL Default
preforkMultiple processes, one thread eachPHP with non-thread-safe extensions, legacy appsRHEL 6 default
workerMultiple processes, each multi-threadedHigh-traffic, mostly static content
eventLike worker but handles keep-alive more efficientlyModern high-concurrency workloadsRHEL 7 default
# Check current MPM:
# httpd -V | grep MPM

# Change MPM in /etc/httpd/conf.modules.d/00-mpm.conf:
LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
# (comment out worker/event, uncomment prefork)

Apache Configuration Hierarchy

# Main config file:
/etc/httpd/conf/httpd.conf          # global settings

# Module configurations:
/etc/httpd/conf.modules.d/*.conf    # loaded module configs

# Virtual host and additional configs:
/etc/httpd/conf.d/*.conf            # server-specific configs (ssl.conf, vhosts)

# Document roots:
/var/www/html/                      # default document root
/var/www/cgi-bin/                   # CGI scripts

# Logs:
/var/log/httpd/access_log           # all requests
/var/log/httpd/error_log            # errors and warnings

Key httpd.conf Directives

# Global settings in /etc/httpd/conf/httpd.conf:

ServerRoot "/etc/httpd"             # base directory for configs
Listen 80                           # port to listen on
ServerName www.example.com:80       # server hostname (must resolve)
ServerAdmin admin@example.com       # admin email (in error pages)

# Performance tuning (prefork MPM):
StartServers 5                      # initial processes at startup
MinSpareServers 5                   # minimum idle processes
MaxSpareServers 10                  # maximum idle processes
MaxRequestWorkers 150               # max simultaneous requests
MaxConnectionsPerChild 1000         # requests per child before respawn

# Timeouts:
Timeout 300                         # timeout for requests (seconds)
KeepAlive On                        # enable persistent connections
MaxKeepAliveRequests 100            # max requests per connection
KeepAliveTimeout 5                  # seconds to wait for next request

# Error pages:
ErrorDocument 404 /errors/404.html
ErrorDocument 500 /errors/500.html

# Security headers:
ServerTokens Prod                   # hide Apache version from headers
ServerSignature Off                 # no version in error pages

Virtual Host Configuration — Complete Guide

Virtual hosting allows one Apache server to serve multiple websites. The three types differ in how Apache determines which site a request is for:

IP-Based Virtual Hosts

Each website has its own dedicated IP address. Apache looks at the destination IP of the TCP connection to determine which VHost to serve. Less common today (IPv4 exhaustion), but still used in high-security environments.

# /etc/httpd/conf.d/vhosts.conf:

# Define VirtualHost on a specific IP:
<VirtualHost 192.168.1.11:80>
    ServerName      server9.example.com
    ServerAlias     server9               # short name
    ServerAdmin     admin@example.com
    DocumentRoot    /var/www/site1
    ErrorLog        /var/log/httpd/site1_error.log
    CustomLog       /var/log/httpd/site1_access.log combined

    <Directory "/var/www/site1">
        Options Indexes FollowSymLinks    # Indexes = show directory listing
        AllowOverride All                 # allow .htaccess overrides
        Require all granted              # allow all access
    </Directory>
</VirtualHost>

Name-Based Virtual Hosts

Multiple websites on the same IP address. Apache uses the HTTP/1.1 Host: header (the hostname in the URL) to determine which VHost to serve. This is the most common type.

<VirtualHost *:80>
    ServerName      www.site1.com
    DocumentRoot    /var/www/site1
</VirtualHost>

<VirtualHost *:80>
    ServerName      www.site2.com
    DocumentRoot    /var/www/site2
</VirtualHost>

# The first VirtualHost block is also the default (used when Host: header doesn't match)

# Create directories and test content:
# mkdir -p /var/www/site1 /var/www/site2
# echo "<h1>Site 1</h1>" > /var/www/site1/index.html
# echo "<h1>Site 2</h1>" > /var/www/site2/index.html

# Add DNS or /etc/hosts entries for testing:
# echo "192.168.1.11 www.site1.com www.site2.com" >> /etc/hosts

# Test:
# curl -H "Host: www.site1.com" http://192.168.1.11
# curl -H "Host: www.site2.com" http://192.168.1.11

Port-Based Virtual Hosts

# Multiple sites on different ports:
Listen 80
Listen 8080
Listen 8443

<VirtualHost *:80>
    ServerName www.main.com
    DocumentRoot /var/www/main
</VirtualHost>

<VirtualHost *:8080>
    ServerName www.dev.com
    DocumentRoot /var/www/dev
</VirtualHost>

HTTPS and SSL/TLS Configuration

# Install mod_ssl:
# yum install mod_ssl -y

# Generate self-signed certificate for testing:
# openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
    -keyout /etc/pki/tls/private/server.key \
    -out /etc/pki/tls/certs/server.crt \
    -subj "/C=US/ST=State/L=City/O=Company/CN=server9.example.com"

# /etc/httpd/conf.d/ssl.conf (key settings):
SSLEngine on
SSLCertificateFile    /etc/pki/tls/certs/server.crt
SSLCertificateKeyFile /etc/pki/tls/private/server.key
# SSLCertificateChainFile /path/to/chain.crt  # for CA-issued certs

# SSL VirtualHost:
<VirtualHost *:443>
    ServerName server9.example.com
    DocumentRoot /var/www/html
    SSLEngine on
    SSLCertificateFile    /etc/pki/tls/certs/server.crt
    SSLCertificateKeyFile /etc/pki/tls/private/server.key
    # Force HTTPS (redirect HTTP to HTTPS):
    # Use in the *:80 VHost:
    # Redirect permanent / https://server9.example.com/
</VirtualHost>

# Test SSL:
# openssl s_client -connect server9.example.com:443
# curl -k https://server9.example.com           # -k = ignore self-signed warning

Access Control

# Apache 2.4 access control (Require directives):

<Directory "/var/www/html/admin">
    Require ip 192.168.1.0/24         # allow only from this subnet
    Require host admin.example.com    # allow from specific hostname
    Require local                     # allow only from localhost
    Require all denied                # deny everyone else
</Directory>

# Allow multiple criteria (OR):
<Directory "/var/www/html/data">
    <RequireAny>
        Require ip 192.168.1.0/24
        Require ip 10.0.0.0/8
    </RequireAny>
</Directory>

# Require ALL criteria (AND):
<Directory "/var/www/html/secure">
    <RequireAll>
        Require ip 192.168.1.0/24
        Require valid-user
    </RequireAll>
</Directory>

# Password protection (Basic Auth):
# htpasswd -c /etc/httpd/.htpasswd admin          # create file, add user
# htpasswd /etc/httpd/.htpasswd user2             # add user to existing file

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

.htaccess Files

.htaccess files allow per-directory configuration without editing the main httpd.conf. Useful for shared hosting where users can't edit the main config, but they have a performance cost (Apache reads .htaccess on every request if AllowOverride is set).

# Enable .htaccess in directory:
<Directory "/var/www/html">
    AllowOverride All                 # allow all directives in .htaccess
    # AllowOverride None              # disable .htaccess (better performance)
    # AllowOverride AuthConfig        # allow only auth directives
</Directory>

# Example .htaccess:
# /var/www/html/.htaccess:
RewriteEngine On
RewriteRule ^(.*)$ /index.php?url=$1 [QSA,L]  # URL rewriting (e.g., for CMS)

CGI and Server-Side Processing

# Enable CGI:
# yum install mod_cgi -y

<Directory "/var/www/cgi-bin">
    Options ExecCGI
    AddHandler cgi-script .cgi .pl .py
</Directory>

# PHP (via mod_php or PHP-FPM):
# yum install php php-fpm -y
# systemctl start php-fpm

# PHP-FPM proxy configuration (modern approach):
<FilesMatch \.php$>
    SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost"
</FilesMatch>

Log Analysis

# Access log format (combined format):
# 192.168.1.10 - admin [07/Jun/2026:14:32:01 +0000] "GET /index.html HTTP/1.1" 200 4521 "http://example.com" "Mozilla/5.0..."
# Fields: client_ip - remote_user [timestamp] "request" status_code bytes_sent "referrer" "user_agent"

# Find most frequent visitors:
# awk '{print $1}' /var/log/httpd/access_log | sort | uniq -c | sort -rn | head -10

# Find 404 errors:
# grep " 404 " /var/log/httpd/access_log

# Find slowest requests (if response time is logged):
# awk '{print $NF, $7}' /var/log/httpd/access_log | sort -rn | head -10

# Real-time log watching:
# tail -f /var/log/httpd/access_log | awk '{print $1, $7, $9}'

# Error log analysis:
# grep "\[error\]" /var/log/httpd/error_log | tail -20

SELinux for Apache

# Apache SELinux contexts:
# Content must have httpd_sys_content_t for Apache to serve it
# chcon -R -t httpd_sys_content_t /var/www/custom/

# For writable content (uploads):
# chcon -R -t httpd_sys_rw_content_t /var/www/uploads/

# Make permanent (survives restorecon):
# semanage fcontext -a -t httpd_sys_content_t "/var/www/custom(/.*)?"
# restorecon -Rv /var/www/custom/

# Important SELinux booleans for Apache:
# httpd_can_network_connect          allow Apache to make network connections
# httpd_can_network_connect_db       allow Apache to connect to databases
# httpd_can_sendmail                 allow Apache to send email
# httpd_enable_cgi                   allow CGI execution
# httpd_use_nfs                      allow serving files from NFS mounts

# Enable a boolean permanently:
# setsebool -P httpd_can_network_connect 1

# Non-standard document root (SELinux):
# semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?"
# restorecon -Rv /srv/www/

Apache Troubleshooting

# Test config syntax:
# httpd -t                           # or: apachectl configtest

# Check what Apache is listening on:
# ss -tulnp | grep httpd

# Test a specific VHost:
# curl -v -H "Host: www.site1.com" http://192.168.1.11

# Common error codes:
# 403 Forbidden   = permissions issue (file permissions, SELinux, Require directive)
# 404 Not Found   = file doesn't exist at DocumentRoot
# 500 Server Error = CGI/PHP script error, look in error_log
# 503 Unavailable = backend (PHP-FPM, proxied app) not responding

# Diagnose 403:
# ls -la /var/www/html/              # check permissions (need r for others)
# ls -la /var/www/                   # check directory permissions (need x)
# getfacl /var/www/html/             # check ACLs
# ls -Z /var/www/html/               # check SELinux context