Configure Apache reverse proxy and load balancing for high availability

Intermediate 45 min Apr 26, 2026 82 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

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.


    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

    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/pki/tls/certs/apache-selfsigned.crt
    SSLCertificateKeyFile /etc/pki/tls/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 /var/log/httpd/loadbalancer_error.log
    CustomLog /var/log/httpd/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 /var/log/httpd/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
# 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
# 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

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
Note: The 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.

Production Security: Never use self-signed certificates in production. Always restrict balancer-manager access to administrative networks only.
# 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

Running this in production?

Want this handled for you? Setting this up once is straightforward. Keeping it patched, monitored, backed up and performant across environments is the harder part. See how we run infrastructure like this for European teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed cloud infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.