Configure Lighttpd 1.4 with mod_proxy for load balancing across multiple backend web servers. Set up health checks, SSL termination, and failover mechanisms for production environments.
Prerequisites
- Root or sudo access
- Multiple backend servers
- Basic understanding of HTTP load balancing
- SSL certificate (optional)
What this solves
Lighttpd 1.4 can distribute incoming HTTP requests across multiple backend servers using mod_proxy and mod_magnet. This setup provides high availability by automatically routing traffic away from failed backends and enables horizontal scaling by adding more servers as needed.
Step-by-step configuration
Install Lighttpd 1.4 with required modules
Start by installing Lighttpd and the proxy modules needed for load balancing functionality.
sudo apt update
sudo apt install -y lighttpd lighttpd-mod-magnet
Enable proxy and required modules
Enable the modules needed for load balancing, health checks, and request routing.
sudo lighttpd-enable-mod proxy
sudo lighttpd-enable-mod magnet
sudo lighttpd-enable-mod rewrite
Create backend server configuration
Configure the backend servers that will handle the actual requests. Create a dedicated configuration file for the load balancer setup.
server.modules += (
"mod_proxy",
"mod_magnet",
"mod_rewrite"
)
Backend server definitions
proxy.server = (
"" => (
"backend1" => (
"host" => "192.168.1.10",
"port" => 8080,
"check-local" => "disable"
),
"backend2" => (
"host" => "192.168.1.11",
"port" => 8080,
"check-local" => "disable"
),
"backend3" => (
"host" => "192.168.1.12",
"port" => 8080,
"check-local" => "disable"
)
)
)
Load balancing method
proxy.balance = "fair"
Connection settings
proxy.forwarded = (
"for" => 1,
"host" => 1,
"proto" => 1,
"remote_user" => 1
)
Timeout settings
proxy.server.timeout = 30
Configure health checks with Lua script
Create a Lua script that implements health checking for backend servers using mod_magnet.
-- Backend health check configuration
local backends = {
{host = "192.168.1.10", port = 8080, name = "backend1"},
{host = "192.168.1.11", port = 8080, name = "backend2"},
{host = "192.168.1.12", port = 8080, name = "backend3"}
}
-- Health check endpoint
local health_path = "/health"
-- Check if backend is healthy
function check_backend_health(backend)
local handle = io.popen("curl -f -s -m 5 http://" .. backend.host .. ":" .. backend.port .. health_path .. " >/dev/null 2>&1; echo $?")
local result = handle:read("*a")
handle:close()
return tonumber(result) == 0
end
-- Main health check logic
function health_check_handler()
local healthy_backends = {}
for i, backend in ipairs(backends) do
if check_backend_health(backend) then
table.insert(healthy_backends, backend)
else
lighty.r.log(3, "Backend " .. backend.name .. " is unhealthy")
end
end
if #healthy_backends == 0 then
lighty.r.status = 503
lighty.r.header["Content-Type"] = "text/plain"
lighty.r.content = "All backend servers are unavailable"
return lighty.RESPONSE_FINISHED
end
return lighty.RESPONSE_GO_ON
end
-- Register the health check handler
lighty.request.env["health_check"] = health_check_handler
Update main configuration for load balancing
Modify the main Lighttpd configuration to include health checking and proper request handling.
server.modules = (
"mod_indexfile",
"mod_access",
"mod_alias",
"mod_redirect",
"mod_rewrite",
"mod_proxy",
"mod_magnet"
)
server.document-root = "/var/www/html"
server.upload-dirs = ( "/var/cache/lighttpd/uploads" )
server.errorlog = "/var/log/lighttpd/error.log"
server.pid-file = "/run/lighttpd.pid"
server.username = "www-data"
server.groupname = "www-data"
server.port = 80
Enable health checking
magnet.attract-physical-path-to = ( "/etc/lighttpd/healthcheck.lua" )
Logging for debugging
accesslog.filename = "/var/log/lighttpd/access.log"
accesslog.format = "%h %V %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %Tf"
Performance tuning
server.max-connections = 1024
server.max-fds = 2048
server.max-worker = 8
index-file.names = ( "index.php", "index.html", "index.lighttpd.html" )
url.access-deny = ( "~", ".inc" )
static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )
compress.cache-dir = "/var/cache/lighttpd/compress/"
compress.filetype = ( "application/javascript", "text/css", "text/html", "text/plain" )
Include additional configurations
include_shell "/usr/share/lighttpd/create-mime.conf.pl"
include_shell "/usr/share/lighttpd/include-conf-enabled.pl"
Set up SSL termination
Configure SSL termination at the load balancer level to handle HTTPS traffic securely.
sudo apt install -y certbot
sudo certbot certonly --standalone -d example.com
Configure SSL in Lighttpd
Enable SSL support and configure certificate paths for secure connections.
sudo lighttpd-enable-mod ssl
server.modules += ( "mod_openssl" )
$HTTP["scheme"] == "https" {
ssl.engine = "enable"
ssl.privkey = "/etc/letsencrypt/live/example.com/privkey.pem"
ssl.ca-file = "/etc/letsencrypt/live/example.com/fullchain.pem"
ssl.cipher-list = "ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!SHA1:!AESCCM"
ssl.honor-cipher-order = "enable"
ssl.disable-client-renegotiation = "enable"
}
Redirect HTTP to HTTPS
$HTTP["scheme"] == "http" {
url.redirect = ("^/(.*)" => "https://example.com/$1")
}
Configure advanced load balancing options
Set up session persistence, request routing, and failover behavior for production use.
# Session persistence based on client IP
proxy.balance = "hash"
proxy.hash-key = "remote_ip"
Request routing rules
$HTTP["url"] =~ "^/api/" {
proxy.server = (
"" => (
"api-backend1" => (
"host" => "192.168.1.20",
"port" => 3000,
"check-local" => "disable"
),
"api-backend2" => (
"host" => "192.168.1.21",
"port" => 3000,
"check-local" => "disable"
)
)
)
}
Static content routing
$HTTP["url"] =~ "\.(css|js|png|jpg|gif|ico)$" {
proxy.server = (
"" => (
"static-backend1" => (
"host" => "192.168.1.30",
"port" => 80,
"check-local" => "disable"
)
)
)
}
Custom headers for backend identification
setenv.add-response-header = (
"X-Load-Balancer" => "lighttpd-1.4",
"X-Frame-Options" => "DENY",
"X-Content-Type-Options" => "nosniff"
)
Create log rotation configuration
Set up log rotation to prevent disk space issues from accumulating logs.
/var/log/lighttpd/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
sharedscripts
postrotate
systemctl reload lighttpd
endscript
}
Set proper file permissions
Ensure correct ownership and permissions for configuration files and logs.
sudo chown -R www-data:www-data /var/log/lighttpd/
sudo chmod 755 /var/log/lighttpd/
sudo chmod 644 /etc/lighttpd/conf-enabled/*.conf
sudo chmod 644 /etc/lighttpd/healthcheck.lua
Test configuration and start services
Validate the configuration syntax and start Lighttpd with the new load balancing setup.
sudo lighttpd -t -f /etc/lighttpd/lighttpd.conf
sudo systemctl restart lighttpd
sudo systemctl enable lighttpd
Configure firewall rules
Open the necessary ports for HTTP, HTTPS, and backend communication.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from 192.168.1.0/24 to any port 8080
Verify your setup
Test the load balancer functionality and health checking mechanisms.
# Check Lighttpd status
sudo systemctl status lighttpd
Test load balancing
curl -H "Host: example.com" http://localhost/
curl -H "Host: example.com" https://localhost/
Check backend distribution
for i in {1..10}; do
curl -s -H "Host: example.com" http://localhost/ | grep -o "Backend: [0-9]*" || echo "Request $i completed"
done
Monitor access logs
sudo tail -f /var/log/lighttpd/access.log
Check error logs
sudo tail -f /var/log/lighttpd/error.log
Configure monitoring and alerting
Set up basic monitoring to track backend server health and load balancer performance.
#!/bin/bash
Backend monitoring script
BACKENDS=("192.168.1.10:8080" "192.168.1.11:8080" "192.168.1.12:8080")
LOG_FILE="/var/log/lighttpd/backend-health.log"
EMAIL_ALERT="admin@example.com"
for backend in "${BACKENDS[@]}"; do
if ! curl -f -s -m 5 "http://$backend/health" >/dev/null 2>&1; then
echo "$(date): Backend $backend is DOWN" >> "$LOG_FILE"
echo "Backend $backend is unreachable" | mail -s "Load Balancer Alert" "$EMAIL_ALERT"
else
echo "$(date): Backend $backend is UP" >> "$LOG_FILE"
fi
done
Schedule monitoring script
Set up automated monitoring using cron to check backend health every minute.
sudo chmod +x /usr/local/bin/monitor-backends.sh
echo " * /usr/local/bin/monitor-backends.sh" | sudo crontab -
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| 503 Service Unavailable | All backend servers down | Check backend health with curl http://backend-ip:port/health |
| Configuration test fails | Syntax error in config | Run sudo lighttpd -t -f /etc/lighttpd/lighttpd.conf for details |
| SSL certificate errors | Certificate path or permissions | Verify paths in SSL config and check sudo ls -la /etc/letsencrypt/live/ |
| Health checks failing | Lua script permissions | Ensure sudo chmod 644 /etc/lighttpd/healthcheck.lua |
| Uneven load distribution | Session persistence enabled | Change proxy.balance = "fair" for round-robin |
| Backend connection timeouts | Network or backend issues | Check proxy.server.timeout and backend response times |
Performance optimization
Fine-tune the load balancer for production workloads and high traffic scenarios.
# Connection tuning
server.max-connections = 4096
server.max-fds = 8192
server.max-worker = 16
server.max-request-size = 65536
Keepalive settings
server.max-keep-alive-requests = 100
server.max-keep-alive-idle = 30
Buffer sizes
server.network-backend = "linux-sendfile"
server.upload-dirs = ( "/var/cache/lighttpd/uploads" )
Proxy optimizations
proxy.server.connect-timeout = 10
proxy.server.timeout = 60
proxy.server.disable-time = 60
Enable compression for proxied content
compress.cache-dir = "/var/cache/lighttpd/compress/"
compress.filetype += ( "application/json", "application/xml" )
Next steps
- Configure Apache reverse proxy and load balancing for high availability for comparison with other load balancers
- Set up Linux storage monitoring with smartmontools and automated health alerts for comprehensive infrastructure monitoring
- Configure centralized logging with rsyslog and logrotate for system monitoring and log management for advanced log aggregation
- Implement Lighttpd clustering with keepalived for high availability
- Configure Lighttpd SSL optimization and security hardening
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Lighttpd Load Balancer Installation Script
# Configures Lighttpd 1.4 with load balancing across multiple backend servers
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Usage function
usage() {
echo "Usage: $0 [backend1_ip:port] [backend2_ip:port] [backend3_ip:port]"
echo "Example: $0 192.168.1.10:8080 192.168.1.11:8080 192.168.1.12:8080"
echo "If no backends specified, will use example IPs"
exit 1
}
# Cleanup function for rollback
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Rolling back changes...${NC}"
systemctl stop lighttpd 2>/dev/null || true
rm -f /etc/lighttpd/conf.d/50-loadbalancer.conf 2>/dev/null || true
rm -f /etc/lighttpd/conf-available/50-loadbalancer.conf 2>/dev/null || true
rm -f /etc/lighttpd/healthcheck.lua 2>/dev/null || true
exit 1
}
trap cleanup ERR
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
# Parse backend arguments or use defaults
BACKENDS=()
if [ $# -eq 0 ]; then
BACKENDS=("192.168.1.10:8080" "192.168.1.11:8080" "192.168.1.12:8080")
echo -e "${YELLOW}No backends specified, using defaults: ${BACKENDS[*]}${NC}"
elif [ $# -eq 3 ]; then
BACKENDS=("$1" "$2" "$3")
else
usage
fi
# Detect distribution
echo "[1/8] Detecting distribution..."
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
LIGHTTPD_USER="www-data"
LIGHTTPD_GROUP="www-data"
CONF_DIR="/etc/lighttpd/conf-available"
ENABLED_DIR="/etc/lighttpd/conf-enabled"
ENABLE_CMD="lighttpd-enable-mod"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
LIGHTTPD_USER="lighttpd"
LIGHTTPD_GROUP="lighttpd"
CONF_DIR="/etc/lighttpd/conf.d"
ENABLED_DIR="/etc/lighttpd/conf.d"
ENABLE_CMD=""
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
LIGHTTPD_USER="lighttpd"
LIGHTTPD_GROUP="lighttpd"
CONF_DIR="/etc/lighttpd/conf.d"
ENABLED_DIR="/etc/lighttpd/conf.d"
ENABLE_CMD=""
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
echo -e "${GREEN}Detected: $PRETTY_NAME${NC}"
else
echo -e "${RED}Cannot detect distribution${NC}"
exit 1
fi
# Update package manager
echo "[2/8] Updating package manager..."
$PKG_UPDATE
# Install Lighttpd and required packages
echo "[3/8] Installing Lighttpd and dependencies..."
if [[ "$ID" == "ubuntu" || "$ID" == "debian" ]]; then
$PKG_INSTALL lighttpd lighttpd-mod-magnet curl lua5.1
else
$PKG_INSTALL lighttpd lighttpd-mod-magnet curl lua
fi
# Create configuration directories if they don't exist
mkdir -p "$CONF_DIR"
mkdir -p "$ENABLED_DIR"
mkdir -p /var/cache/lighttpd/uploads
mkdir -p /var/log/lighttpd
# Set proper ownership
chown -R $LIGHTTPD_USER:$LIGHTTPD_GROUP /var/cache/lighttpd
chown -R $LIGHTTPD_USER:$LIGHTTPD_GROUP /var/log/lighttpd
# Enable required modules for Debian-based systems
if [[ "$ID" == "ubuntu" || "$ID" == "debian" ]]; then
echo "[4/8] Enabling Lighttpd modules..."
$ENABLE_CMD proxy
$ENABLE_CMD magnet
$ENABLE_CMD rewrite
fi
# Create backend server configuration
echo "[5/8] Creating load balancer configuration..."
cat > "$CONF_DIR/50-loadbalancer.conf" << EOF
server.modules += (
"mod_proxy",
"mod_magnet",
"mod_rewrite"
)
# Backend server definitions
proxy.server = (
"" => (
EOF
# Add backends to configuration
for i in "${!BACKENDS[@]}"; do
IFS=':' read -r host port <<< "${BACKENDS[$i]}"
cat >> "$CONF_DIR/50-loadbalancer.conf" << EOF
"backend$((i+1))" => (
"host" => "$host",
"port" => $port,
"check-local" => "disable"
),
EOF
done
# Complete the configuration
cat >> "$CONF_DIR/50-loadbalancer.conf" << 'EOF'
)
)
# Load balancing method
proxy.balance = "fair"
# Connection settings
proxy.forwarded = (
"for" => 1,
"host" => 1,
"proto" => 1,
"remote_user" => 1
)
# Timeout settings
proxy.server.timeout = 30
# Enable health checking
magnet.attract-physical-path-to = ( "/etc/lighttpd/healthcheck.lua" )
# Access logging
accesslog.filename = "/var/log/lighttpd/access.log"
EOF
# Set proper permissions on config file
chmod 644 "$CONF_DIR/50-loadbalancer.conf"
chown root:root "$CONF_DIR/50-loadbalancer.conf"
# Enable configuration for Debian-based systems
if [[ "$ID" == "ubuntu" || "$ID" == "debian" ]]; then
ln -sf "../conf-available/50-loadbalancer.conf" "$ENABLED_DIR/50-loadbalancer.conf"
fi
# Create health check Lua script
echo "[6/8] Creating health check script..."
cat > /etc/lighttpd/healthcheck.lua << 'EOF'
-- Backend health check configuration
local backends = {
EOF
# Add backends to Lua script
for i in "${!BACKENDS[@]}"; do
IFS=':' read -r host port <<< "${BACKENDS[$i]}"
cat >> /etc/lighttpd/healthcheck.lua << EOF
{host = "$host", port = $port, name = "backend$((i+1))"},
EOF
done
cat >> /etc/lighttpd/healthcheck.lua << 'EOF'
}
-- Health check endpoint
local health_path = "/health"
-- Check if backend is healthy
function check_backend_health(backend)
local handle = io.popen("curl -f -s -m 5 http://" .. backend.host .. ":" .. backend.port .. health_path .. " >/dev/null 2>&1; echo $?")
local result = handle:read("*a")
handle:close()
return tonumber(result) == 0
end
-- Main health check logic
function health_check_handler()
local healthy_backends = {}
for i, backend in ipairs(backends) do
if check_backend_health(backend) then
table.insert(healthy_backends, backend)
else
lighty.r.log(3, "Backend " .. backend.name .. " is unhealthy")
end
end
if #healthy_backends == 0 then
lighty.r.status = 503
lighty.r.header["Content-Type"] = "text/plain"
lighty.r.content = "All backend servers are unavailable"
return lighty.RESPONSE_FINISHED
end
return lighty.RESPONSE_GO_ON
end
-- Register the health check handler
if lighty.request and lighty.request.env then
lighty.request.env["health_check"] = health_check_handler
end
EOF
# Set proper permissions on Lua script
chmod 644 /etc/lighttpd/healthcheck.lua
chown root:root /etc/lighttpd/healthcheck.lua
# Configure firewall
echo "[7/8] Configuring firewall..."
if command -v ufw >/dev/null 2>&1; then
ufw --force enable
ufw allow 80/tcp
ufw allow 443/tcp
elif command -v firewall-cmd >/dev/null 2>&1; then
systemctl enable --now firewalld
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
fi
# Start and enable Lighttpd
echo "[8/8] Starting and enabling Lighttpd..."
systemctl enable lighttpd
systemctl restart lighttpd
# Verify installation
echo "Verifying installation..."
if systemctl is-active --quiet lighttpd; then
echo -e "${GREEN}✓ Lighttpd is running${NC}"
else
echo -e "${RED}✗ Lighttpd failed to start${NC}"
systemctl status lighttpd
exit 1
fi
if lighttpd -t -f /etc/lighttpd/lighttpd.conf; then
echo -e "${GREEN}✓ Configuration is valid${NC}"
else
echo -e "${RED}✗ Configuration validation failed${NC}"
exit 1
fi
# Display configuration summary
echo -e "\n${GREEN}Load balancer installation completed successfully!${NC}"
echo -e "\nConfiguration summary:"
echo "- Load balancer listening on port 80"
echo "- Backend servers:"
for i in "${!BACKENDS[@]}"; do
echo " - Backend$((i+1)): ${BACKENDS[$i]}"
done
echo "- Configuration files:"
echo " - Main config: $CONF_DIR/50-loadbalancer.conf"
echo " - Health check: /etc/lighttpd/healthcheck.lua"
echo "- Log files:"
echo " - Access: /var/log/lighttpd/access.log"
echo " - Error: /var/log/lighttpd/error.log"
echo ""
echo "Next steps:"
echo "1. Ensure your backend servers are running on the specified ports"
echo "2. Configure a /health endpoint on each backend server"
echo "3. Test the load balancer: curl http://localhost/"
Review the script before running. Execute with: bash install.sh