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:
- Listens on a TCP port (default 80 for HTTP, 443 for HTTPS)
- Accepts incoming HTTP connections from clients (browsers, curl, APIs)
- Parses the HTTP request (method, URL, headers)
- Maps the URL to a file on disk or a dynamic handler (CGI, PHP, proxied app)
- 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):
| MPM | Model | Use Case | RHEL Default |
|---|---|---|---|
| prefork | Multiple processes, one thread each | PHP with non-thread-safe extensions, legacy apps | RHEL 6 default |
| worker | Multiple processes, each multi-threaded | High-traffic, mostly static content | |
| event | Like worker but handles keep-alive more efficiently | Modern high-concurrency workloads | RHEL 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