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
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
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
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"
Configure reverse proxy with load balancing
Create the main configuration file that defines the backend server pool and load balancing rules with health checks.
ServerName example.com
# Redirect all HTTP traffic to HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
ServerName example.com
# SSL Configuration
SSLEngine on
SSLCertificateFile /etc/ssl/certs/apache-selfsigned.crt
SSLCertificateKeyFile /etc/ssl/private/apache-selfsigned.key
# Security headers
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"
# Define backend server cluster
ProxyPreserveHost On
ProxyRequests Off
# Backend server 1
BalancerMember http://203.0.113.10:8080 status=+H
# Backend server 2
BalancerMember http://203.0.113.11:8080 status=+H
# Backend server 3
BalancerMember http://203.0.113.12:8080 status=+H
# Load balancing method
ProxySet lbmethod=byrequests
# Health check settings
ProxySet hcmethod=GET
ProxySet hcuri=/health
ProxySet hcinterval=30
ProxySet retry=300
# Proxy all requests to backend cluster
ProxyPass / balancer://backend-cluster/
ProxyPassReverse / balancer://backend-cluster/
# Balancer manager (restrict access in production)
SetHandler balancer-manager
Require ip 127.0.0.1
Require ip 10.0.0.0/8
Require ip 172.16.0.0/12
Require ip 192.168.0.0/16
ProxyPass /balancer-manager !
# Logging
ErrorLog ${APACHE_LOG_DIR}/loadbalancer_error.log
CustomLog ${APACHE_LOG_DIR}/loadbalancer_access.log combined
# Log backend server responses
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %D %{BALANCER_WORKER_NAME}e" balancer
CustomLog ${APACHE_LOG_DIR}/loadbalancer_backend.log balancer
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
SetHandler server-status
Require ip 127.0.0.1
Require ip 10.0.0.0/8
Require ip 172.16.0.0/12
Require ip 192.168.0.0/16
Proxy timeout settings
ProxyTimeout 300
ProxyPreserveHost On
Connection pooling
ProxyIOBufferSize 65536
Proxy pass settings for different application contexts
ProxyPass balancer://api-cluster/
ProxyPassReverse balancer://api-cluster/
# API-specific headers
ProxyPassReverse /api/ balancer://api-cluster/api/
ProxyPassReverseRewrite balancer://api-cluster/api/
# Static content can bypass load balancer
ProxyPass balancer://static-cluster/
ProxyPassReverse balancer://static-cluster/
# Cache static content
ExpiresActive On
ExpiresDefault "access plus 1 month"
Define API backend cluster
BalancerMember http://203.0.113.20:3000 status=+H route=api1
BalancerMember http://203.0.113.21:3000 status=+H route=api2
ProxySet lbmethod=byrequests
ProxySet hcmethod=GET
ProxySet hcuri=/api/health
ProxySet hcinterval=15
ProxySet retry=180
Define static content cluster
BalancerMember http://203.0.113.30:80 status=+H
BalancerMember http://203.0.113.31:80 status=+H
ProxySet lbmethod=byrequests
ProxySet hcmethod=HEAD
ProxySet hcuri=/favicon.ico
ProxySet hcinterval=60
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
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
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
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
View load balancer logs
sudo tail -f /var/log/apache2/loadbalancer_access.log
sudo tail -f /var/log/apache2/loadbalancer_backend.log
Test backend health checks
sudo systemctl status loadbalancer-health.timer
sudo journalctl -u loadbalancer-health.service -f
Load balancing algorithms and options
Apache mod_proxy_balancer supports multiple load balancing methods and configuration options for different use cases.
| Algorithm | Configuration | Use Case |
|---|---|---|
| Round Robin | ProxySet lbmethod=byrequests |
Default method, distributes requests evenly |
| Weighted Round Robin | BalancerMember http://server1:8080 loadfactor=3 |
Give more requests to powerful servers |
| Least Connections | ProxySet lbmethod=bytraffic |
Route to server with least active connections |
| IP Hash | Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e" |
Sticky sessions for stateful applications |
status=+H flag enables health checks for backend members. Remove this flag if your backends don't support health check endpoints.Common issues
| Symptom | Cause | Fix |
|---|---|---|
| 502 Bad Gateway errors | Backend servers unreachable | Check backend server status and firewall rules: curl http://backend-ip:port |
| SSL certificate warnings | Self-signed certificate | Replace with proper SSL certificate or add CA to client trust store |
| Balancer manager not accessible | Access restricted by IP | Add your IP to allowed ranges in <Location "/balancer-manager"> |
| All requests go to one server | Health checks failing on other servers | Check backend health endpoints: curl http://backend-ip:port/health |
| Apache won't start | Configuration syntax error | Run sudo apache2ctl configtest and fix reported errors |
| High memory usage | Too many persistent connections | Tune MaxConnectionsPerChild and connection pooling settings |
Security hardening
Additional security measures to implement in production environments.
# Additional security headers
Header always set X-XSS-Protection "1; mode=block"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
Hide Apache version
ServerTokens Prod
ServerSignature Off
Disable unnecessary HTTP methods
Require all denied
Rate limiting (requires mod_evasive)
LoadModule evasive24_module modules/mod_evasive24.so
DOSHashTableSize 2048
DOSPageCount 5
DOSSiteCount 100
DOSPageInterval 2
DOSSiteInterval 2
DOSBlockingPeriod 60
Performance tuning
Optimize Apache for high-traffic load balancing scenarios.
# Tune for load balancing workload
StartServers 16
MinSpareServers 16
MaxSpareServers 32
MaxConnectionsPerChild 10000
MaxRequestWorkers 400
# Alternative: worker MPM for better performance
StartServers 4
MinSpareThreads 16
MaxSpareThreads 64
ThreadLimit 64
ThreadsPerChild 32
MaxRequestWorkers 1024
MaxConnectionsPerChild 10000
Monitoring and logging integration
Apache reverse proxy integrates well with centralized logging and monitoring systems. You can forward Apache logs to ELK stack for centralized logging or set up centralized logging with rsyslog for better observability.
Next steps
- Configure HAProxy SSL termination with Let's Encrypt and security headers
- Setup Tomcat clustering with HAProxy load balancing
- Implement Apache web application firewall with ModSecurity
- Configure Apache rate limiting and DDoS protection
- Monitor Apache performance with Prometheus and Grafana
Running this in production?
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