Configure H2O HTTP/2 web server caching and compression optimization for high performance

Intermediate 45 min Apr 29, 2026 85 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Optimize H2O web server performance with advanced caching strategies, HTTP/2 compression, and production-grade tuning for high-traffic applications.

Prerequisites

  • Root or sudo access
  • Basic understanding of web servers
  • SSL certificates (for HTTPS)
  • Backend application (for proxy caching)

What this solves

H2O web server offers excellent HTTP/2 performance out of the box, but proper caching and compression configuration can dramatically improve response times and reduce bandwidth usage. This tutorial shows you how to configure advanced caching policies, optimize compression settings, and tune H2O for high-traffic production workloads.

Step-by-step configuration

Install H2O web server

Start by installing H2O and creating the basic directory structure for our configuration.

sudo apt update
sudo apt install -y h2o
sudo mkdir -p /etc/h2o/conf.d
sudo dnf update -y
sudo dnf install -y h2o
sudo mkdir -p /etc/h2o/conf.d

Create main H2O configuration

Configure the main H2O server with HTTP/2 enabled and basic security settings.

user: h2o
pid-file: /var/run/h2o/h2o.pid
error-log: /var/log/h2o/error.log
access-log:
  path: /var/log/h2o/access.log
  format: "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-agent}i\" %{duration}x"

HTTP/2 and performance settings

http2-idle-timeout: 10 http2-max-concurrent-requests-per-connection: 100 max-connections: 1024 num-threads: 4

Enable compression globally

compress: ON hosts: "example.com:80": listen: port: 80 paths: "/": file.dir: /var/www/html "example.com:443": listen: port: 443 ssl: certificate-file: /etc/ssl/certs/example.com.pem key-file: /etc/ssl/private/example.com.key paths: "/": file.dir: /var/www/html

Configure advanced caching directives

Create a dedicated caching configuration that handles different content types with appropriate cache policies.

# Static asset caching configuration
cache-control:
  - extensions: [css, js, png, jpg, jpeg, gif, ico, svg, woff, woff2, ttf]
    header: "Cache-Control: public, max-age=31536000, immutable"
  - extensions: [html, htm]
    header: "Cache-Control: public, max-age=3600, must-revalidate"
  - extensions: [json, xml]
    header: "Cache-Control: public, max-age=1800"
  - extensions: [pdf, doc, docx]
    header: "Cache-Control: public, max-age=86400"

Enable ETags for better cache validation

file.etag: ON

Memory-based file cache

file.send-gzip: ON file.dir-listing: OFF

Configure expires headers

expires: - ".css": "1 year" - ".js": "1 year" - ".png": "1 year" - ".jpg": "1 year" - ".jpeg": "1 year" - ".gif": "1 year" - ".ico": "1 year" - ".svg": "1 year" - ".woff": "1 year" - ".woff2": "1 year" - ".ttf": "1 year" - ".html": "1 hour" - ".json": "30 minutes"

Configure compression optimization

Set up advanced compression settings with proper MIME type handling and compression levels.

# Compression configuration
compress:
  - gzip: 
      level: 6
      min-size: 1024
  - brotli:
      level: 6
      min-size: 1024

MIME types to compress

compress-mime-types: - text/html - text/css - text/javascript - text/xml - text/plain - text/csv - application/javascript - application/json - application/xml - application/rss+xml - application/atom+xml - image/svg+xml - application/x-font-ttf - application/vnd.ms-fontobject - font/opentype

Don't compress already compressed formats

compress-exclude: - image/png - image/jpeg - image/gif - video/mp4 - video/mpeg - audio/mp3 - application/zip - application/gzip - application/pdf

Configure proxy caching for dynamic content

Set up proxy caching for applications behind H2O with intelligent cache invalidation.

# Proxy caching configuration
proxy.reverse.url: "http://127.0.0.1:8080"

Enable proxy caching

proxy.cache: ON proxy.cache.size: 128m proxy.cache.dir: /var/cache/h2o

Cache rules for different paths

proxy.cache.rules: - path: /api/ cache: OFF - path: /admin/ cache: OFF - path: /static/ max-age: 86400 - path: /images/ max-age: 604800 - path: / max-age: 3600 vary: "Accept-Encoding, Cookie"

Bypass cache for authenticated users

proxy.cache.bypass: - "$cookie_sessionid" - "$http_authorization" - "$arg_nocache"

Update main configuration with includes

Modify the main H2O configuration to include our caching and compression settings.

user: h2o
pid-file: /var/run/h2o/h2o.pid
error-log: /var/log/h2o/error.log
access-log:
  path: /var/log/h2o/access.log
  format: "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-agent}i\" %{duration}x %{cache-status}x"

HTTP/2 and performance settings

http2-idle-timeout: 10 http2-max-concurrent-requests-per-connection: 100 max-connections: 1024 num-threads: 4 send-server-name: OFF

Include compression and caching configs

include: /etc/h2o/conf.d/compression.conf hosts: "example.com:80": listen: port: 80 paths: "/": file.dir: /var/www/html include: /etc/h2o/conf.d/caching.conf "/app": include: /etc/h2o/conf.d/proxy-cache.conf "example.com:443": listen: port: 443 ssl: certificate-file: /etc/ssl/certs/example.com.pem key-file: /etc/ssl/private/example.com.key cipher-suite: ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305 cipher-preference: server header.add: "Strict-Transport-Security: max-age=31536000; includeSubDomains" header.add: "X-Frame-Options: DENY" header.add: "X-Content-Type-Options: nosniff" paths: "/": file.dir: /var/www/html include: /etc/h2o/conf.d/caching.conf "/app": include: /etc/h2o/conf.d/proxy-cache.conf

Create cache directory and set permissions

Set up the proxy cache directory with correct ownership and permissions.

sudo mkdir -p /var/cache/h2o
sudo chown h2o:h2o /var/cache/h2o
sudo chmod 755 /var/cache/h2o
sudo mkdir -p /var/log/h2o
sudo chown h2o:h2o /var/log/h2o
sudo chmod 755 /var/log/h2o

Configure log rotation

Set up log rotation to prevent disk space issues with access logs.

/var/log/h2o/*.log {
    daily
    missingok
    rotate 52
    compress
    delaycompress
    notifempty
    create 644 h2o h2o
    postrotate
        if [ -f /var/run/h2o/h2o.pid ]; then
            kill -USR1 cat /var/run/h2o/h2o.pid
        fi
    endscript
}

Create performance monitoring script

Set up a monitoring script to track cache hit rates and performance metrics.

#!/bin/bash

H2O Performance Monitoring Script

LOG_FILE="/var/log/h2o/access.log" OUTPUT_FILE="/var/log/h2o/stats.log"

Get current timestamp

TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

Calculate cache hit rate from last 1000 requests

CACHE_HITS=$(tail -n 1000 $LOG_FILE | grep -c "HIT") CACHE_MISSES=$(tail -n 1000 $LOG_FILE | grep -c "MISS") TOTAL_REQUESTS=$((CACHE_HITS + CACHE_MISSES)) if [ $TOTAL_REQUESTS -gt 0 ]; then HIT_RATE=$(echo "scale=2; $CACHE_HITS / $TOTAL_REQUESTS * 100" | bc -l) else HIT_RATE="0.00" fi

Calculate average response time

AVG_RESPONSE=$(tail -n 1000 $LOG_FILE | awk '{sum += $NF} END {if (NR > 0) print sum/NR; else print 0}')

Get current connections

CURRENT_CONN=$(ss -tuln | grep -E ':80|:443' | wc -l)

Output statistics

echo "[$TIMESTAMP] Cache Hit Rate: ${HIT_RATE}%, Avg Response: ${AVG_RESPONSE}ms, Active Connections: $CURRENT_CONN" >> $OUTPUT_FILE

Keep only last 1000 lines

tail -n 1000 $OUTPUT_FILE > $OUTPUT_FILE.tmp && mv $OUTPUT_FILE.tmp $OUTPUT_FILE
sudo chmod +x /usr/local/bin/h2o-stats.sh
sudo chown h2o:h2o /usr/local/bin/h2o-stats.sh

Set up automated cache warming

Create a cache warming script to pre-populate frequently accessed content.

#!/bin/bash

H2O Cache Warming Script

BASE_URL="https://example.com" USER_AGENT="H2O-Cache-Warmer/1.0"

List of URLs to warm

URLS=( "/" "/about" "/products" "/contact" "/static/css/main.css" "/static/js/main.js" "/api/popular-items" ) echo "Starting cache warming at $(date)" for url in "${URLS[@]}"; do echo "Warming: ${BASE_URL}${url}" curl -s -H "User-Agent: $USER_AGENT" "${BASE_URL}${url}" > /dev/null sleep 1 done echo "Cache warming completed at $(date)"
sudo chmod +x /usr/local/bin/cache-warm.sh
sudo chown h2o:h2o /usr/local/bin/cache-warm.sh

Configure systemd timer for monitoring

Set up automated monitoring and cache warming with systemd timers.

[Unit]
Description=H2O Performance Monitor
After=h2o.service

[Service]
Type=oneshot
User=h2o
ExecStart=/usr/local/bin/h2o-stats.sh
StandardOutput=journal
StandardError=journal
[Unit]
Description=Run H2O Performance Monitor every 5 minutes
Requires=h2o-monitor.service

[Timer]
OnCalendar=*:0/5
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now h2o-monitor.timer

Test configuration and restart H2O

Validate the configuration syntax and restart H2O with the new settings.

sudo h2o -t -c /etc/h2o/h2o.conf
sudo systemctl restart h2o
sudo systemctl enable h2o

Performance tuning for high traffic

Configure system-level optimizations

Optimize kernel parameters for high-performance web serving.

# Network performance tuning
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 5000
net.core.rmem_default = 262144
net.core.rmem_max = 16777216
net.core.wmem_default = 262144
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 12582912 16777216
net.ipv4.tcp_wmem = 4096 12582912 16777216
net.ipv4.tcp_max_syn_backlog = 8096
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10240 65535

File descriptor limits

fs.file-max = 2097152
sudo sysctl -p /etc/sysctl.d/99-h2o.conf

Configure service limits

Increase file descriptor limits for the H2O service.

[Service]
LimitNOFILE=65535
LimitNPROC=32768
sudo mkdir -p /etc/systemd/system/h2o.service.d/
sudo systemctl daemon-reload
sudo systemctl restart h2o

Verify your setup

Check that H2O is running with optimized configuration and verify caching is working.

sudo systemctl status h2o
sudo h2o -t -c /etc/h2o/h2o.conf

Test HTTP/2 support

curl -I -H "Accept-Encoding: gzip, br" https://example.com

Check cache statistics

sudo tail -f /var/log/h2o/stats.log

Test compression

curl -H "Accept-Encoding: gzip" -I https://example.com/static/css/main.css

Monitor active connections

sudo ss -tuln | grep -E ':80|:443'

Check cache directory

sudo ls -la /var/cache/h2o/

Verify timer is running

sudo systemctl status h2o-monitor.timer

Monitoring and testing

Load testing with performance validation

Use Apache Bench to test the server under load and validate caching performance.

# Install Apache Bench
sudo apt install -y apache2-utils  # Ubuntu/Debian

sudo dnf install -y httpd-tools # AlmaLinux/Rocky

Test concurrent connections

ab -n 1000 -c 100 https://example.com/

Test static asset caching

ab -n 500 -c 50 https://example.com/static/css/main.css

Monitor during test

watch -n 1 'sudo ss -tuln | grep -E ":80|:443" | wc -l'

Set up Prometheus monitoring

Configure Prometheus to collect H2O metrics for long-term monitoring.

# Metrics endpoint
status:
  - port: 9090
    bind: 127.0.0.1
    

Add to main config under paths:

"/metrics": status: ON header.add: "Content-Type: text/plain"

For comprehensive monitoring, you can set up Prometheus and Grafana monitoring stack to track H2O performance metrics over time.

Common issues

SymptomCauseFix
Cache not workingIncorrect cache directory permissionssudo chown -R h2o:h2o /var/cache/h2o
High memory usageCache size too largeReduce proxy.cache.size in configuration
Compression not appliedFile size below min-size thresholdAdjust min-size in compression config
SSL handshake errorsCipher suite compatibilityUpdate cipher-suite list in SSL config
Connection timeoutsSystem limits too lowIncrease max-connections and system limits
Log files growing too largeMissing log rotationVerify logrotate configuration is active
Cache warming failsScript permissions or network issuesCheck script ownership and SSL certificate validity

Next steps

Running this in production?

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

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

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