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

Advanced 45 min Apr 29, 2026 537 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

Don't want to manage this yourself?

We handle infrastructure for businesses that depend on uptime. Fully managed, with one fixed contact who knows your setup.

You get one fixed contact who knows your setup

Rotterdam 05:05 · reachable in a message, no ticket form