Configure H2O HTTP/2 server load balancing with health checks and SSL termination

Advanced 45 min Apr 29, 2026 78 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

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
sudo dnf install -y epel-release
sudo dnf config-manager --add-repo https://h2o.examp1e.net/configure
sudo dnf 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
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="192.168.1.0/24" port protocol="tcp" port="8080" accept'
sudo firewall-cmd --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

SymptomCauseFix
H2O fails to startConfiguration syntax errorsudo h2o -t -c /etc/h2o/h2o.conf to check config
SSL certificate errorsWrong file permissions or pathsudo chmod 600 /etc/h2o/ssl/*.key and verify paths
Backend servers unreachableNetwork connectivity or firewallTest with telnet backend-ip port and check firewall rules
Health checks failingIncorrect health check pathVerify backend health endpoints return 200 status
High memory usageToo many worker processesAdjust worker process count in main config
SSL handshake failuresCipher suite mismatchUpdate cipher-suite setting to include client-supported ciphers
Load balancing not workingAll backends marked unhealthyCheck /var/log/h2o/health-check.log for specific errors

Next steps

Running this in production?

Want this handled for you? Running this at scale adds a second layer of work: capacity planning, failover drills, cost control, and on-call. 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 private cloud infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.