Set up H2O as a high-performance load balancer with HTTP/2 support, automated health monitoring, and SSL termination for production web applications.
Prerequisites
- Root or sudo access
- Multiple backend servers running web applications
- Basic understanding of HTTP/HTTPS protocols
- SSL certificates (self-signed or valid)
What this solves
H2O is a high-performance HTTP/2 web server that excels at load balancing with built-in health checks and SSL termination. This tutorial configures H2O to distribute traffic across multiple backend servers while monitoring their health and handling SSL encryption at the load balancer level.
Step-by-step installation
Install H2O web server
H2O requires adding the official repository for the latest version with HTTP/2 support.
wget -O - https://h2o.examp1e.net/configure | sudo sh
sudo apt update
sudo apt install -y h2o
Create SSL certificate directory
Set up directory structure for SSL certificates with proper permissions.
sudo mkdir -p /etc/h2o/ssl
sudo chmod 750 /etc/h2o/ssl
sudo chown root:h2o /etc/h2o/ssl
Generate self-signed SSL certificate
Create a test certificate for initial setup. Replace with proper certificates in production.
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/h2o/ssl/server.key \
-out /etc/h2o/ssl/server.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=example.com"
sudo chmod 600 /etc/h2o/ssl/server.key
sudo chmod 644 /etc/h2o/ssl/server.crt
Configure main H2O configuration
Create the primary configuration file with load balancing and SSL settings.
user: h2o
pid-file: /var/run/h2o.pid
error-log: /var/log/h2o/error.log
access-log: /var/log/h2o/access.log
SSL/TLS Configuration
ssl-session-resumption:
mode: ticket
ticket-store: file
ticket-file: /var/lib/h2o/session-ticket
Backend servers definition
proxy.preserve-host: ON
proxy.timeout.io: 30000
proxy.timeout.keepalive: 5000
Load balancer upstream definitions
upstreams:
backend-cluster:
- server: 192.168.1.10:8080
weight: 1
- server: 192.168.1.11:8080
weight: 1
- server: 192.168.1.12:8080
weight: 1
Health check configuration
health-check:
interval: 5
timeout: 3
healthy-threshold: 2
unhealthy-threshold: 3
path: /health
expected-status: 200
hosts:
# HTTP redirect to HTTPS
"*:80":
listen:
port: 80
paths:
"/":
redirect:
status: 301
url: "https://example.com/"
# HTTPS with load balancing
"example.com:443":
listen:
port: 443
ssl:
certificate-file: /etc/h2o/ssl/server.crt
key-file: /etc/h2o/ssl/server.key
cipher-suite: "ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!SHA1:!AESCCM"
minimum-version: TLSv1.2
header.add: "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload"
header.add: "X-Frame-Options: SAMEORIGIN"
header.add: "X-Content-Type-Options: nosniff"
header.add: "X-XSS-Protection: 1; mode=block"
paths:
"/":
proxy.reverse.url: "http://backend-cluster"
proxy.preserve-host: ON
proxy.proxy-protocol: OFF
Create advanced load balancing configuration
Configure multiple load balancing algorithms and failover policies.
# Advanced upstream configuration with different algorithms
upstreams:
web-servers:
balancer: round-robin
servers:
- server: 192.168.1.20:80
weight: 3
max-fails: 3
fail-timeout: 10s
- server: 192.168.1.21:80
weight: 2
max-fails: 3
fail-timeout: 10s
- server: 192.168.1.22:80
weight: 1
backup: true
api-servers:
balancer: least-conn
servers:
- server: 192.168.1.30:3000
weight: 1
- server: 192.168.1.31:3000
weight: 1
Health check configurations
health-checks:
web-health:
interval: 10s
timeout: 5s
path: "/health"
expected-status: 200
healthy-threshold: 2
unhealthy-threshold: 3
api-health:
interval: 5s
timeout: 3s
path: "/api/health"
expected-status: 200
expected-body: "OK"
healthy-threshold: 1
unhealthy-threshold: 2
Circuit breaker settings
circuit-breaker:
failure-threshold: 5
recovery-timeout: 30s
half-open-max-requests: 10
Configure detailed health monitoring
Set up comprehensive health checks with different strategies for various backend types.
# Database backend health check
health-check database:
upstream: db-cluster
interval: 15s
timeout: 10s
path: "/db-health"
expected-status: 200
headers:
"User-Agent": "H2O-HealthCheck/1.0"
"Accept": "application/json"
healthy-threshold: 2
unhealthy-threshold: 4
Application server health check
health-check application:
upstream: app-cluster
interval: 8s
timeout: 5s
path: "/status"
expected-status: 200
expected-body-regex: "status.*ok"
healthy-threshold: 1
unhealthy-threshold: 3
Static content server health check
health-check static:
upstream: static-cluster
interval: 30s
timeout: 10s
path: "/ping"
expected-status: 200
healthy-threshold: 3
unhealthy-threshold: 5
Failover configuration
failover:
strategy: "fail-fast"
max-retries: 2
retry-interval: 1s
backup-server-delay: 5s
Configure SSL security headers and HSTS
Add comprehensive security headers and SSL optimization for production use.
# SSL Security Configuration
ssl-session-resumption:
mode: all
ticket-store: memcached
memcached-servers:
- "127.0.0.1:11211"
session-timeout: 3600
OCSP Stapling
ocsp-stapling:
enabled: true
cache-size: 100
cache-timeout: 3600
Security Headers Template
security-headers: &security-headers
header.add: "Strict-Transport-Security: max-age=63072000; includeSubDomains; preload"
header.add: "X-Frame-Options: DENY"
header.add: "X-Content-Type-Options: nosniff"
header.add: "X-XSS-Protection: 1; mode=block"
header.add: "Referrer-Policy: strict-origin-when-cross-origin"
header.add: "Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
header.add: "Permissions-Policy: geolocation=(), microphone=(), camera=()"
Rate limiting
rate-limit:
requests-per-minute: 1000
burst-size: 100
status-code: 429
response-body: "Rate limit exceeded. Please try again later."
Compression settings
compress:
gzip: ON
brotli: ON
minimum-size: 1024
Create logging and monitoring configuration
Set up detailed logging for load balancer performance analysis and debugging.
# Enhanced logging configuration
access-log:
path: "/var/log/h2o/access.log"
format: '%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-agent}i" %D %{upstream_response_time}x %{upstream_addr}x %{ssl_protocol}x %{ssl_cipher}x'
error-log:
path: "/var/log/h2o/error.log"
level: warn
Health check logging
health-check-log:
path: "/var/log/h2o/health-check.log"
format: '%t [%l] %{upstream}x %{status}x %{response_time}x %{check_type}x: %m'
Performance metrics logging
metrics-log:
path: "/var/log/h2o/metrics.log"
interval: 60s
format: 'timestamp=%t active_connections=%{active_connections}x requests_per_second=%{rps}x response_time_avg=%{response_time_avg}x'
Load balancer status logging
status-log:
path: "/var/log/h2o/status.log"
interval: 30s
include:
- upstream_status
- backend_health
- connection_pools
- ssl_sessions
Set up log rotation
Configure automatic log rotation to prevent disk space issues.
/var/log/h2o/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 0644 h2o h2o
postrotate
systemctl reload h2o
endscript
}
Create systemd service configuration
Configure H2O as a systemd service with proper resource limits and security settings.
[Unit]
Description=H2O HTTP/2 server
After=network.target
Wants=network.target
[Service]
Type=forking
PIDFile=/var/run/h2o.pid
ExecStartPre=/usr/bin/h2o -t -c /etc/h2o/h2o.conf
ExecStart=/usr/bin/h2o -c /etc/h2o/h2o.conf
ExecReload=/bin/kill -HUP $MAINPID
KillMode=mixed
KillSignal=SIGTERM
PrivateTmp=true
LimitNOFILE=65536
LimitNPROC=32768
User=h2o
Group=h2o
[Install]
WantedBy=multi-user.target
Create H2O user and directories
Set up the dedicated user account and directory structure with proper permissions.
sudo useradd -r -s /bin/false -d /var/lib/h2o h2o
sudo mkdir -p /var/lib/h2o /var/log/h2o /etc/h2o/conf.d
sudo chown h2o:h2o /var/lib/h2o /var/log/h2o
sudo chmod 750 /var/lib/h2o /var/log/h2o
sudo chown root:h2o /etc/h2o /etc/h2o/conf.d
sudo chmod 750 /etc/h2o /etc/h2o/conf.d
Enable and start H2O service
Reload systemd configuration and start the H2O service with automatic startup enabled.
sudo systemctl daemon-reload
sudo systemctl enable h2o
sudo systemctl start h2o
Configure firewall rules
Open necessary ports for HTTP, HTTPS, and health check endpoints.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from 192.168.1.0/24 to any port 8080
sudo ufw reload
Verify your setup
Test the H2O configuration and load balancing functionality.
# Check H2O configuration syntax
sudo h2o -t -c /etc/h2o/h2o.conf
Verify service status
sudo systemctl status h2o
Test HTTP to HTTPS redirect
curl -I http://example.com
Test SSL certificate and HTTP/2
curl -I -k https://example.com
Check load balancer status
curl -k https://example.com/h2o-status
Monitor health checks
sudo tail -f /var/log/h2o/health-check.log
Test backend failover
sudo systemctl stop nginx # on one backend server
curl -k https://example.com # should still work
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| H2O fails to start | Configuration syntax error | sudo h2o -t -c /etc/h2o/h2o.conf to check config |
| SSL certificate errors | Wrong file permissions or path | sudo chmod 600 /etc/h2o/ssl/*.key and verify paths |
| Backend servers unreachable | Network connectivity or firewall | Test with telnet backend-ip port and check firewall rules |
| Health checks failing | Incorrect health check path | Verify backend health endpoints return 200 status |
| High memory usage | Too many worker processes | Adjust worker process count in main config |
| SSL handshake failures | Cipher suite mismatch | Update cipher-suite setting to include client-supported ciphers |
| Load balancing not working | All backends marked unhealthy | Check /var/log/h2o/health-check.log for specific errors |
Next steps
- Configure NGINX reverse proxy with SSL termination for comparison with H2O setup
- Set up HAProxy high availability with keepalived clustering for additional load balancer redundancy
- Monitor H2O performance with Prometheus and Grafana dashboards
- Implement H2O WAF integration with ModSecurity for enhanced security
- Configure H2O HTTP/3 and QUIC support for next-generation protocols
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# H2O Load Balancer Installation Script
# Configures H2O with SSL termination, load balancing, and health checks
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Variables
DOMAIN=${1:-"example.com"}
BACKEND_IPS=${2:-"192.168.1.10:8080,192.168.1.11:8080,192.168.1.12:8080"}
usage() {
echo "Usage: $0 [domain] [backend_ips]"
echo " domain: Domain name for SSL certificate (default: example.com)"
echo " backend_ips: Comma-separated backend servers (default: 192.168.1.10:8080,192.168.1.11:8080,192.168.1.12:8080)"
echo "Example: $0 mydomain.com 10.0.1.10:8080,10.0.1.11:8080"
exit 1
}
log() {
echo -e "${GREEN}[INFO]${NC} $1"
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1"
exit 1
}
cleanup() {
warn "Installation failed. Rolling back changes..."
systemctl stop h2o 2>/dev/null || true
rm -f /etc/h2o/h2o.conf.backup 2>/dev/null || true
if [ -f /etc/h2o/h2o.conf.orig ]; then
mv /etc/h2o/h2o.conf.orig /etc/h2o/h2o.conf 2>/dev/null || true
fi
}
trap cleanup ERR
check_prerequisites() {
echo "[1/8] Checking prerequisites..."
if [ "$EUID" -ne 0 ]; then
error "This script must be run as root"
fi
if ! command -v wget &> /dev/null; then
error "wget is required but not installed"
fi
log "Prerequisites check passed"
}
detect_distro() {
echo "[2/8] Detecting distribution..."
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update"
FIREWALL_CMD="ufw"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf makecache"
FIREWALL_CMD="firewall-cmd"
;;
fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf makecache"
FIREWALL_CMD="firewall-cmd"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum makecache"
FIREWALL_CMD="firewall-cmd"
;;
*)
error "Unsupported distribution: $ID"
;;
esac
else
error "Cannot detect distribution - /etc/os-release not found"
fi
log "Detected distribution: $ID using $PKG_MGR"
}
install_h2o() {
echo "[3/8] Installing H2O web server..."
case "$PKG_MGR" in
apt)
wget -qO- https://h2o.examp1e.net/configure | sh
$PKG_UPDATE
$PKG_INSTALL h2o
;;
dnf|yum)
$PKG_INSTALL epel-release
if [ "$PKG_MGR" = "dnf" ]; then
dnf config-manager --add-repo https://h2o.examp1e.net/configure
else
yum-config-manager --add-repo https://h2o.examp1e.net/configure
fi
$PKG_INSTALL h2o
;;
esac
log "H2O installed successfully"
}
setup_ssl_directory() {
echo "[4/8] Setting up SSL certificate directory..."
mkdir -p /etc/h2o/ssl
chmod 750 /etc/h2o/ssl
if getent group h2o >/dev/null 2>&1; then
chown root:h2o /etc/h2o/ssl
else
chown root:root /etc/h2o/ssl
fi
mkdir -p /var/lib/h2o
chmod 755 /var/lib/h2o
if getent passwd h2o >/dev/null 2>&1; then
chown h2o:h2o /var/lib/h2o
fi
log "SSL directory created"
}
generate_ssl_certificate() {
echo "[5/8] Generating self-signed SSL certificate..."
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/h2o/ssl/server.key \
-out /etc/h2o/ssl/server.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=$DOMAIN" 2>/dev/null
chmod 600 /etc/h2o/ssl/server.key
chmod 644 /etc/h2o/ssl/server.crt
if getent group h2o >/dev/null 2>&1; then
chown root:h2o /etc/h2o/ssl/server.*
fi
log "SSL certificate generated for $DOMAIN"
}
configure_h2o() {
echo "[6/8] Configuring H2O load balancer..."
if [ -f /etc/h2o/h2o.conf ]; then
cp /etc/h2o/h2o.conf /etc/h2o/h2o.conf.orig
fi
# Convert comma-separated backend IPs to YAML format
backend_servers=""
IFS=',' read -ra BACKENDS <<< "$BACKEND_IPS"
for backend in "${BACKENDS[@]}"; do
backend_servers="$backend_servers - server: $backend"$'\n'" weight: 1"$'\n'
done
cat > /etc/h2o/h2o.conf << EOF
user: h2o
pid-file: /var/run/h2o.pid
error-log: /var/log/h2o/error.log
access-log: /var/log/h2o/access.log
# SSL/TLS Configuration
ssl-session-resumption:
mode: ticket
ticket-store: file
ticket-file: /var/lib/h2o/session-ticket
# Backend servers configuration
proxy.preserve-host: ON
proxy.timeout.io: 30000
proxy.timeout.keepalive: 5000
# Load balancer upstream definitions
upstreams:
backend-cluster:
$backend_servers
# Health check configuration
health-check:
interval: 5
timeout: 3
healthy-threshold: 2
unhealthy-threshold: 3
path: /health
expected-status: 200
hosts:
# HTTP redirect to HTTPS
"*:80":
listen:
port: 80
paths:
"/":
redirect:
status: 301
url: "https://$DOMAIN/"
# HTTPS with load balancing
"$DOMAIN:443":
listen:
port: 443
ssl:
certificate-file: /etc/h2o/ssl/server.crt
key-file: /etc/h2o/ssl/server.key
cipher-suite: "ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!SHA1:!AESCCM"
minimum-version: TLSv1.2
header.add: "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload"
header.add: "X-Frame-Options: SAMEORIGIN"
header.add: "X-Content-Type-Options: nosniff"
header.add: "X-XSS-Protection: 1; mode=block"
paths:
"/":
proxy.reverse.url: "http://backend-cluster"
proxy.preserve-host: ON
proxy.proxy-protocol: OFF
EOF
chmod 644 /etc/h2o/h2o.conf
chown root:root /etc/h2o/h2o.conf
log "H2O configuration created"
}
configure_firewall() {
echo "[7/8] Configuring firewall..."
case "$FIREWALL_CMD" in
ufw)
if systemctl is-active --quiet ufw; then
ufw allow 80/tcp
ufw allow 443/tcp
log "UFW rules added for ports 80 and 443"
fi
;;
firewall-cmd)
if systemctl is-active --quiet firewalld; then
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
log "Firewalld rules added for HTTP and HTTPS"
fi
;;
esac
# Configure SELinux if present
if command -v setsebool &> /dev/null; then
setsebool -P httpd_can_network_connect 1 2>/dev/null || true
log "SELinux configured for network connections"
fi
}
start_and_verify() {
echo "[8/8] Starting H2O and verifying installation..."
systemctl enable h2o
systemctl start h2o
sleep 2
if ! systemctl is-active --quiet h2o; then
error "H2O failed to start. Check logs: journalctl -u h2o"
fi
if ss -tlnp | grep -q ":80\|:443"; then
log "H2O is listening on ports 80 and 443"
else
error "H2O is not listening on expected ports"
fi
log "H2O load balancer installed and started successfully!"
warn "Replace the self-signed certificate with a proper SSL certificate in production"
warn "Update backend server IPs in /etc/h2o/h2o.conf as needed"
log "Configuration file: /etc/h2o/h2o.conf"
log "View logs: journalctl -u h2o -f"
}
# Main execution
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
if [[ $# -gt 2 ]]; then
usage
fi
check_prerequisites
detect_distro
install_h2o
setup_ssl_directory
generate_ssl_certificate
configure_h2o
configure_firewall
start_and_verify
fi
Review the script before running. Execute with: bash install.sh