Set up Apache as a reverse proxy with load balancing across multiple backend servers. Configure health checks, SSL termination, and failover for production high availability deployments.
Prerequisites
- Root or sudo access
- Multiple backend servers for testing
- Basic understanding of HTTP and networking
What this solves
Apache reverse proxy with load balancing distributes incoming requests across multiple backend servers, improving performance and availability. This configuration provides SSL termination, health monitoring, and automatic failover when backend servers become unavailable.
Step-by-step configuration
Update system packages
Start by updating your package manager to ensure you get the latest versions of Apache and required modules.
sudo apt update && sudo apt upgrade -y
sudo dnf update -y
Install Apache HTTP server
Install Apache web server which will act as the reverse proxy and load balancer for your backend applications.
sudo apt install -y apache2
sudo dnf install -y httpd
Enable required Apache modules
Enable the proxy, proxy_http, proxy_balancer, lbmethod_byrequests, and headers modules for reverse proxy functionality and load balancing.
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_balancer
sudo a2enmod lbmethod_byrequests
sudo a2enmod headers
sudo a2enmod ssl
sudo sed -i 's/#LoadModule proxy_module/LoadModule proxy_module/' /etc/httpd/conf.modules.d/00-proxy.conf
sudo sed -i 's/#LoadModule proxy_http_module/LoadModule proxy_http_module/' /etc/httpd/conf.modules.d/00-proxy.conf
sudo sed -i 's/#LoadModule proxy_balancer_module/LoadModule proxy_balancer_module/' /etc/httpd/conf.modules.d/00-proxy.conf
sudo sed -i 's/#LoadModule lbmethod_byrequests_module/LoadModule lbmethod_byrequests_module/' /etc/httpd/conf.modules.d/00-proxy.conf
sudo sed -i 's/#LoadModule ssl_module/LoadModule ssl_module/' /etc/httpd/conf.modules.d/00-ssl.conf
Create SSL certificates
Generate self-signed SSL certificates for testing, or place your purchased certificates in the Apache SSL directory.
sudo mkdir -p /etc/ssl/private
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/apache-selfsigned.key \
-out /etc/ssl/certs/apache-selfsigned.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=example.com"
sudo mkdir -p /etc/pki/tls/private
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/pki/tls/private/apache-selfsigned.key \
-out /etc/pki/tls/certs/apache-selfsigned.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=example.com"
Configure reverse proxy with load balancing
Create the main configuration file that defines the backend server pool and load balancing rules with health checks.
Configure advanced load balancing options
Create a separate configuration file for advanced load balancing features including sticky sessions and failover settings.
# Advanced load balancer configuration
# Enable mod_status for monitoring
# Proxy timeout settings
ProxyTimeout 300
ProxyPreserveHost On
# Connection pooling
ProxyIOBufferSize 65536
# Proxy pass settings for different application contexts
# Define API backend cluster
# Define static content cluster
# Advanced load balancer configuration
# Enable mod_status for monitoring
# Proxy timeout settings
ProxyTimeout 300
ProxyPreserveHost On
# Connection pooling
ProxyIOBufferSize 65536
# Proxy pass settings for different application contexts
# Define API backend cluster
# Define static content cluster
Enable the site configuration
Enable the load balancer site and disable the default Apache site to prevent conflicts.
sudo a2ensite loadbalancer.conf
sudo a2enconf balancer-advanced.conf
sudo a2dissite 000-default
sudo a2enmod rewrite
sudo a2enmod expires
# Configuration files are automatically loaded from conf.d/
# Enable rewrite module
echo "LoadModule rewrite_module modules/mod_rewrite.so" | sudo tee -a /etc/httpd/conf.modules.d/00-base.conf
echo "LoadModule expires_module modules/mod_expires.so" | sudo tee -a /etc/httpd/conf.modules.d/00-base.conf
Configure firewall rules
Open the necessary ports for HTTP, HTTPS, and management access while securing the balancer manager interface.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from 10.0.0.0/8 to any port 80
sudo ufw allow from 172.16.0.0/12 to any port 80
sudo ufw allow from 192.168.0.0/16 to any port 80
sudo ufw --force enable
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" service name="http" accept'
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.16.0.0/12" service name="http" accept'
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.0.0/16" service name="http" accept'
sudo firewall-cmd --reload
Test configuration and start Apache
Validate the Apache configuration syntax and start the service with the new load balancer settings.
sudo apache2ctl configtest
sudo systemctl enable apache2
sudo systemctl restart apache2
sudo systemctl status apache2
sudo httpd -t
sudo systemctl enable httpd
sudo systemctl restart httpd
sudo systemctl status httpd
Configure health check monitoring
Set up a monitoring script that checks the load balancer status and backend health automatically.
#!/bin/bash
# Load balancer health check script
LOG_FILE="/var/log/loadbalancer-health.log"
EMAIL="admin@example.com"
BALANCER_URL="http://localhost/balancer-manager"
# Function to log messages
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}
# Check Apache service status
if ! systemctl is-active --quiet apache2 httpd 2>/dev/null; then
log_message "ERROR: Apache service is not running"
echo "Apache load balancer is down on $(hostname)" | mail -s "Load Balancer Alert" "$EMAIL"
exit 1
fi
# Check if balancer manager is accessible
if ! curl -s "$BALANCER_URL" > /dev/null; then
log_message "WARNING: Balancer manager not accessible"
fi
# Check backend server connectivity
BACKENDS=("http://203.0.113.10:8080/health" "http://203.0.113.11:8080/health" "http://203.0.113.12:8080/health")
FAILED_BACKENDS=()
for backend in "${BACKENDS[@]}"; do
if ! curl -s -f "$backend" -m 10 > /dev/null; then
FAILED_BACKENDS+=("$backend")
log_message "ERROR: Backend $backend is not responding"
fi
done
# Alert if more than half of backends are down
if [ ${#FAILED_BACKENDS[@]} -gt $((${#BACKENDS[@]} / 2)) ]; then
log_message "CRITICAL: More than half of backend servers are down"
echo "Critical: Multiple backend servers down on $(hostname): ${FAILED_BACKENDS[*]}" | mail -s "Load Balancer Critical Alert" "$EMAIL"
fi
log_message "Health check completed - Active backends: $((${#BACKENDS[@]} - ${#FAILED_BACKENDS[@]}))/${#BACKENDS[@]}"
exit 0
Set up automated monitoring
Create a systemd timer to run the health check script every 5 minutes and make it executable.
sudo chmod +x /usr/local/bin/check-loadbalancer.sh
sudo chown root:root /usr/local/bin/check-loadbalancer.sh
[Unit]
Description=Load Balancer Health Check
After=network.target
[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/check-loadbalancer.sh
StandardOutput=journal
StandardError=journal
[Unit]
Description=Run Load Balancer Health Check every 5 minutes
Requires=loadbalancer-health.service
[Timer]
OnBootSec=5min
OnUnitActiveSec=5min
AccuracySec=1s
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable loadbalancer-health.timer
sudo systemctl start loadbalancer-health.timer
Verify your setup
Test the load balancer configuration and verify that requests are being distributed across backend servers.
# Check Apache configuration
sudo apache2ctl configtest
# Verify Apache is running and listening on correct ports
sudo systemctl status apache2
sudo netstat -tlnp | grep :80
sudo netstat -tlnp | grep :443
# Test HTTP to HTTPS redirect
curl -I http://example.com
# Test HTTPS load balancing
curl -k -I https://example.com
# Check balancer manager (replace with your server IP)
curl http://127.0.0.1/balancer-manager
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Global variables
DOMAIN_NAME="${1:-example.com}"
BACKEND_SERVERS=("${2:-203.0.113.10:8080}" "${3:-203.0.113.11:8080}" "${4:-203.0.113.12:8080}")
usage() {
echo "Usage: $0 [domain_name] [backend1:port] [backend2:port] [backend3:port]"
echo "Example: $0 example.com 10.0.1.10:8080 10.0.1.11:8080 10.0.1.12:8080"
exit 1
}
log() {
echo -e "${GREEN}[INFO]${NC} $1"
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
cleanup() {
error "Installation failed. Rolling back changes..."
if [[ "$PKG_MGR" == "apt" && -f "/etc/apache2/sites-available/${DOMAIN_NAME}.conf" ]]; then
rm -f "/etc/apache2/sites-available/${DOMAIN_NAME}.conf"
a2dissite "${DOMAIN_NAME}" 2>/dev/null || true
elif [[ "$PKG_MGR" != "apt" && -f "/etc/httpd/conf.d/${DOMAIN_NAME}.conf" ]]; then
rm -f "/etc/httpd/conf.d/${DOMAIN_NAME}.conf"
fi
systemctl reload "$SERVICE_NAME" 2>/dev/null || true
exit 1
}
trap cleanup ERR
check_prerequisites() {
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root or with sudo"
exit 1
fi
if ! command -v openssl &> /dev/null; then
error "OpenSSL is required but not installed"
exit 1
fi
}
detect_distro() {
if [[ ! -f /etc/os-release ]]; then
error "Cannot detect distribution: /etc/os-release not found"
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
SERVICE_NAME="apache2"
APACHE_CONFIG_DIR="/etc/apache2"
SSL_CERT_DIR="/etc/ssl/certs"
SSL_KEY_DIR="/etc/ssl/private"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
SERVICE_NAME="httpd"
APACHE_CONFIG_DIR="/etc/httpd"
SSL_CERT_DIR="/etc/pki/tls/certs"
SSL_KEY_DIR="/etc/pki/tls/private"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
SERVICE_NAME="httpd"
APACHE_CONFIG_DIR="/etc/httpd"
SSL_CERT_DIR="/etc/pki/tls/certs"
SSL_KEY_DIR="/etc/pki/tls/private"
;;
*)
error "Unsupported distribution: $ID"
exit 1
;;
esac
}
update_system() {
log "[1/8] Updating system packages..."
$PKG_UPDATE
}
install_apache() {
log "[2/8] Installing Apache HTTP server..."
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL apache2
else
$PKG_INSTALL httpd
fi
}
enable_modules() {
log "[3/8] Enabling required Apache modules..."
if [[ "$PKG_MGR" == "apt" ]]; then
a2enmod proxy proxy_http proxy_balancer lbmethod_byrequests headers ssl rewrite
else
# Enable modules in RHEL-based systems
sed -i 's/#LoadModule proxy_module/LoadModule proxy_module/' "$APACHE_CONFIG_DIR/conf.modules.d/00-proxy.conf" 2>/dev/null || true
sed -i 's/#LoadModule proxy_http_module/LoadModule proxy_http_module/' "$APACHE_CONFIG_DIR/conf.modules.d/00-proxy.conf" 2>/dev/null || true
sed -i 's/#LoadModule proxy_balancer_module/LoadModule proxy_balancer_module/' "$APACHE_CONFIG_DIR/conf.modules.d/00-proxy.conf" 2>/dev/null || true
sed -i 's/#LoadModule lbmethod_byrequests_module/LoadModule lbmethod_byrequests_module/' "$APACHE_CONFIG_DIR/conf.modules.d/00-proxy.conf" 2>/dev/null || true
sed -i 's/#LoadModule ssl_module/LoadModule ssl_module/' "$APACHE_CONFIG_DIR/conf.modules.d/00-ssl.conf" 2>/dev/null || true
sed -i 's/#LoadModule rewrite_module/LoadModule rewrite_module/' "$APACHE_CONFIG_DIR/conf.modules.d/00-base.conf" 2>/dev/null || true
fi
}
create_ssl_certificates() {
log "[4/8] Creating SSL certificates..."
mkdir -p "$SSL_KEY_DIR" "$SSL_CERT_DIR"
chmod 755 "$SSL_KEY_DIR" "$SSL_CERT_DIR"
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout "$SSL_KEY_DIR/apache-selfsigned.key" \
-out "$SSL_CERT_DIR/apache-selfsigned.crt" \
-subj "/C=US/ST=State/L=City/O=Organization/CN=$DOMAIN_NAME"
chmod 644 "$SSL_CERT_DIR/apache-selfsigned.crt"
chmod 600 "$SSL_KEY_DIR/apache-selfsigned.key"
}
configure_reverse_proxy() {
log "[5/8] Configuring reverse proxy with load balancing..."
# Build backend members configuration
BACKEND_MEMBERS=""
for server in "${BACKEND_SERVERS[@]}"; do
BACKEND_MEMBERS="${BACKEND_MEMBERS} BalancerMember http://${server} status=+H\n"
done
if [[ "$PKG_MGR" == "apt" ]]; then
CONFIG_FILE="/etc/apache2/sites-available/${DOMAIN_NAME}.conf"
else
CONFIG_FILE="/etc/httpd/conf.d/${DOMAIN_NAME}.conf"
fi
cat > "$CONFIG_FILE" << EOF
<VirtualHost *:80>
ServerName $DOMAIN_NAME
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>
<VirtualHost *:443>
ServerName $DOMAIN_NAME
SSLEngine on
SSLCertificateFile $SSL_CERT_DIR/apache-selfsigned.crt
SSLCertificateKeyFile $SSL_KEY_DIR/apache-selfsigned.key
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set X-Frame-Options DENY
Header always set X-Content-Type-Options nosniff
Header always set Referrer-Policy "strict-origin-when-cross-origin"
ProxyPreserveHost On
ProxyRequests Off
<Proxy balancer://backend-cluster>
$(echo -e "$BACKEND_MEMBERS")
ProxySet lbmethod=byrequests
ProxySet hcmethod=GET
ProxySet hcuri=/health
ProxySet hcinterval=30
ProxySet retry=300
</Proxy>
ProxyPass /balancer-manager !
ProxyPass / balancer://backend-cluster/
ProxyPassReverse / balancer://backend-cluster/
<Location "/balancer-manager">
SetHandler balancer-manager
Require local
</Location>
LogLevel warn
ErrorLog \${APACHE_LOG_DIR}/error.log
CustomLog \${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
EOF
chmod 644 "$CONFIG_FILE"
if [[ "$PKG_MGR" == "apt" ]]; then
a2ensite "$DOMAIN_NAME"
a2dissite 000-default 2>/dev/null || true
fi
}
start_services() {
log "[6/8] Starting and enabling Apache service..."
systemctl enable "$SERVICE_NAME"
systemctl start "$SERVICE_NAME"
}
configure_firewall() {
log "[7/8] Configuring firewall..."
if command -v ufw &> /dev/null; then
ufw allow 'Apache Full' 2>/dev/null || true
elif command -v firewall-cmd &> /dev/null; then
firewall-cmd --permanent --add-service=http --add-service=https 2>/dev/null || true
firewall-cmd --reload 2>/dev/null || true
fi
}
verify_installation() {
log "[8/8] Verifying installation..."
if ! systemctl is-active --quiet "$SERVICE_NAME"; then
error "Apache service is not running"
exit 1
fi
if ! systemctl is-enabled --quiet "$SERVICE_NAME"; then
error "Apache service is not enabled"
exit 1
fi
if ! ss -tlnp | grep -q ":443.*httpd\|:443.*apache"; then
error "Apache is not listening on port 443"
exit 1
fi
log "Apache reverse proxy with load balancing installed successfully!"
log "Domain: $DOMAIN_NAME"
log "Backend servers: ${BACKEND_SERVERS[*]}"
log "Balancer manager: https://$DOMAIN_NAME/balancer-manager"
warn "Using self-signed SSL certificate. Replace with valid certificate for production."
}
main() {
check_prerequisites
detect_distro
update_system
install_apache
enable_modules
create_ssl_certificates
configure_reverse_proxy
start_services
configure_firewall
verify_installation
}
main "$@"
Review the script before running. Execute with: bash install.sh