Deploy H2O, a high-performance HTTP/2 web server with native FastCGI support and SSL/TLS termination. Configure virtual hosts, PHP-FPM integration, and performance optimization for production environments.
Prerequisites
- Root or sudo access
- Basic Linux command line knowledge
- Understanding of web server concepts
What this solves
H2O is a modern, high-performance web server designed for HTTP/2 and HTTPS from the ground up. It provides faster response times than traditional web servers while consuming less memory and CPU resources. This tutorial shows you how to install H2O, configure virtual hosts with SSL certificates, set up FastCGI integration with PHP-FPM, and optimize performance for production workloads.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest security patches and dependencies.
sudo apt update && sudo apt upgrade -y
Install build dependencies
H2O requires compilation tools and SSL libraries. Install the necessary development packages for building from source.
sudo apt install -y build-essential cmake pkg-config libssl-dev zlib1g-dev libyaml-dev
Create H2O user and directories
Create a dedicated system user for H2O and set up the required directory structure with proper ownership.
sudo useradd --system --home /var/lib/h2o --shell /bin/false h2o
sudo mkdir -p /etc/h2o /var/log/h2o /var/lib/h2o /var/www/html
sudo chown h2o:h2o /var/log/h2o /var/lib/h2o
sudo chown www-data:www-data /var/www/html
Download and compile H2O from source
Download the latest stable release of H2O and compile it with HTTP/2 and SSL support enabled.
cd /tmp
wget https://github.com/h2o/h2o/archive/v2.2.6.tar.gz
tar xzf v2.2.6.tar.gz
cd h2o-2.2.6
cmake -DWITH_BUNDLED_SSL=on -DCMAKE_INSTALL_PREFIX=/usr/local .
make -j$(nproc)
sudo make install
Install PHP-FPM for FastCGI support
Install PHP-FPM to handle dynamic content through FastCGI. This provides better performance than traditional CGI.
sudo apt install -y php-fpm php-mysql php-curl php-gd php-mbstring php-xml php-zip
Create H2O main configuration
Set up the primary H2O configuration file with basic settings for HTTP/2, SSL, and logging.
user: h2o
pid-file: /var/run/h2o.pid
error-log: /var/log/h2o/error.log
access-log: /var/log/h2o/access.log
Global settings
max-connections: 1024
num-threads: 4
http2-idle-timeout: 10
http2-graceful-shutdown-timeout: 30
Default host
hosts:
"example.com:80":
listen:
port: 80
paths:
"/":
file.dir: /var/www/html
file.index: ['index.html', 'index.php']
fastcgi.connect:
port: 9000
type: tcp
fastcgi.spawn: "php-fpm"
"example.com:443":
listen:
port: 443
ssl:
certificate-file: /etc/ssl/certs/example.com.crt
key-file: /etc/ssl/private/example.com.key
cipher-suite: "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256"
cipher-preference: server
paths:
"/":
file.dir: /var/www/html
file.index: ['index.html', 'index.php']
fastcgi.connect:
port: 9000
type: tcp
fastcgi.spawn: "php-fpm"
header.add: "Strict-Transport-Security: max-age=31536000; includeSubDomains"
header.add: "X-Frame-Options: DENY"
header.add: "X-Content-Type-Options: nosniff"
Generate SSL certificates
Create self-signed SSL certificates for testing. In production, use certificates from a trusted Certificate Authority or Let's Encrypt.
sudo mkdir -p /etc/ssl/private
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/example.com.key \
-out /etc/ssl/certs/example.com.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=example.com"
sudo chmod 600 /etc/ssl/private/example.com.key
sudo chmod 644 /etc/ssl/certs/example.com.crt
Configure PHP-FPM pool
Optimize the PHP-FPM pool configuration for better performance with H2O.
[www]
user = www-data
group = www-data
listen = 127.0.0.1:9000
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
security.limit_extensions = .php
Create systemd service file
Set up a systemd service to manage H2O with automatic restart and proper dependency management.
[Unit]
Description=H2O HTTP/2 Server
After=network.target remote-fs.target nss-lookup.target
Wants=network.target
[Service]
Type=simple
User=h2o
Group=h2o
ExecStart=/usr/local/bin/h2o -c /etc/h2o/h2o.conf
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
KillSignal=SIGTERM
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/h2o /var/run
NoNewPrivileges=true
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Create test PHP file
Create a simple PHP file to test FastCGI integration and verify that dynamic content processing works correctly.
sudo chown www-data:www-data /var/www/html/info.php
sudo chmod 644 /var/www/html/info.php
Configure log rotation
Set up logrotate to manage H2O log files and prevent disk space issues.
/var/log/h2o/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 644 h2o h2o
postrotate
/bin/kill -s USR1 cat /var/run/h2o.pid 2> /dev/null 2> /dev/null || true
endscript
}
Start and enable services
Start PHP-FPM and H2O services, then enable them to start automatically on system boot.
sudo systemctl daemon-reload
sudo systemctl enable --now php8.1-fpm
sudo systemctl enable --now h2o
sudo systemctl status h2o
sudo systemctl status php8.1-fpm
Configure firewall rules
Open the necessary ports for HTTP and HTTPS traffic while maintaining security.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload
Performance optimization
Optimize system limits
Increase system limits for file descriptors and connections to handle high traffic loads.
h2o soft nofile 65536
h2o hard nofile 65536
h2o soft nproc 32768
h2o hard nproc 32768
Tune kernel parameters
Optimize network and memory settings for better HTTP/2 performance. These settings improve connection handling and reduce latency.
net.core.somaxconn = 65536
net.ipv4.tcp_max_syn_backlog = 65536
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 60
net.ipv4.tcp_keepalive_probes = 3
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
sudo sysctl -p /etc/sysctl.d/99-h2o.conf
Security hardening
Configure HTTP security headers
Add security headers to protect against common web vulnerabilities. Update your H2O configuration with enhanced security settings.
# Security headers configuration
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=()"
Hide server version
header.add: "Server: H2O"
Set up access logging
Configure detailed access logging for security monitoring and performance analysis.
# Enhanced access log format
access-log:
path: /var/log/h2o/access.log
format: '%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-agent}i" %{duration}x %{ssl.protocol-version}x'
For additional security, consider integrating H2O with AppArmor security profiles to enforce mandatory access controls.
Verify your setup
Test your H2O installation to ensure HTTP/2, SSL, and FastCGI are working correctly.
# Check H2O service status
sudo systemctl status h2o
Verify HTTP/2 support
curl -I --http2 -k https://localhost/
Test FastCGI with PHP
curl -k https://localhost/info.php | head -20
Check SSL certificate
openssl s_client -connect localhost:443 -servername example.com < /dev/null
Monitor connections and performance
sudo netstat -tlnp | grep h2o
sudo journalctl -u h2o -f
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| H2O fails to start | Configuration syntax error | sudo /usr/local/bin/h2o -t -c /etc/h2o/h2o.conf |
| PHP files download instead of executing | FastCGI not configured correctly | Check PHP-FPM status and port 9000 binding |
| SSL certificate errors | Wrong certificate path or permissions | Verify certificate files exist and have correct ownership |
| High CPU usage | Too many worker threads | Adjust num-threads to match CPU cores |
| Connection refused errors | Firewall blocking ports | Check firewall rules for ports 80 and 443 |
| File permission denied | Wrong file ownership | sudo chown -R www-data:www-data /var/www/html |
Next steps
- Set up NGINX reverse proxy with SSL certificates for load balancing multiple H2O instances
- Optimize Linux network stack performance for better HTTP/2 throughput
- Configure H2O load balancing with health checks for high availability setups
- Integrate H2O with Let's Encrypt for automatic SSL certificates
- Configure H2O caching and compression optimization for better performance
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Default values
DOMAIN="${1:-example.com}"
H2O_VERSION="v2.2.6"
# Usage function
usage() {
echo "Usage: $0 [domain_name]"
echo "Example: $0 mywebsite.com"
exit 1
}
# Cleanup function for rollback
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Cleaning up...${NC}"
systemctl stop h2o 2>/dev/null || true
systemctl disable h2o 2>/dev/null || true
rm -rf /tmp/h2o-* /etc/systemd/system/h2o.service
systemctl daemon-reload 2>/dev/null || true
}
trap cleanup ERR
# Logging
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Validate input
if [[ $# -gt 1 ]]; then
usage
fi
# Check if running as root or with sudo
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root or with sudo"
exit 1
fi
log_info "Installing H2O HTTP/2 web server for domain: $DOMAIN"
# [1/10] Detect distribution
echo -e "${BLUE}[1/10] Detecting distribution...${NC}"
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 && apt upgrade -y"
APACHE_USER="www-data"
PHP_FPM_SERVICE="php*-fpm"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
APACHE_USER="apache"
PHP_FPM_SERVICE="php-fpm"
# Check if dnf exists, fallback to yum
if ! command -v dnf &> /dev/null; then
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
fi
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
APACHE_USER="apache"
PHP_FPM_SERVICE="php-fpm"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
log_success "Detected $PRETTY_NAME"
else
log_error "Cannot detect distribution"
exit 1
fi
# [2/10] Update system packages
echo -e "${BLUE}[2/10] Updating system packages...${NC}"
$PKG_UPDATE
# [3/10] Install build dependencies
echo -e "${BLUE}[3/10] Installing build dependencies...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL build-essential cmake pkg-config libssl-dev zlib1g-dev libyaml-dev wget tar
else
$PKG_INSTALL groupinstall "Development Tools"
$PKG_INSTALL cmake pkgconfig openssl-devel zlib-devel libyaml-devel wget tar
fi
# [4/10] Create H2O user and directories
echo -e "${BLUE}[4/10] Creating H2O user and directories...${NC}"
useradd --system --home /var/lib/h2o --shell /bin/false h2o 2>/dev/null || true
mkdir -p /etc/h2o /var/log/h2o /var/lib/h2o /var/www/html
chown h2o:h2o /var/log/h2o /var/lib/h2o
chown $APACHE_USER:$APACHE_USER /var/www/html
chmod 755 /var/log/h2o /var/lib/h2o /var/www/html
# [5/10] Download and compile H2O
echo -e "${BLUE}[5/10] Downloading and compiling H2O...${NC}"
cd /tmp
wget -q "https://github.com/h2o/h2o/archive/${H2O_VERSION}.tar.gz"
tar xzf "${H2O_VERSION}.tar.gz"
cd "h2o-${H2O_VERSION#v}"
cmake -DWITH_BUNDLED_SSL=on -DCMAKE_INSTALL_PREFIX=/usr/local .
make -j$(nproc)
make install
# [6/10] Install PHP-FPM
echo -e "${BLUE}[6/10] Installing PHP-FPM...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL php-fpm php-mysql php-curl php-gd php-mbstring php-xml php-zip
else
$PKG_INSTALL php-fpm php-mysqlnd php-curl php-gd php-mbstring php-xml php-zip
fi
# Start and enable PHP-FPM
systemctl enable $PHP_FPM_SERVICE
systemctl start $PHP_FPM_SERVICE
# [7/10] Generate SSL certificates
echo -e "${BLUE}[7/10] Generating SSL certificates...${NC}"
mkdir -p /etc/ssl/private
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout "/etc/ssl/private/${DOMAIN}.key" \
-out "/etc/ssl/certs/${DOMAIN}.crt" \
-subj "/C=US/ST=State/L=City/O=Organization/CN=${DOMAIN}" 2>/dev/null
chmod 600 "/etc/ssl/private/${DOMAIN}.key"
chmod 644 "/etc/ssl/certs/${DOMAIN}.crt"
chown root:root "/etc/ssl/private/${DOMAIN}.key" "/etc/ssl/certs/${DOMAIN}.crt"
# [8/10] Create H2O configuration
echo -e "${BLUE}[8/10] Creating H2O configuration...${NC}"
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
# Global settings
max-connections: 1024
num-threads: 4
http2-idle-timeout: 10
http2-graceful-shutdown-timeout: 30
hosts:
"${DOMAIN}:80":
listen:
port: 80
paths:
"/":
file.dir: /var/www/html
file.index: ['index.html', 'index.php']
fastcgi.connect:
port: 9000
type: tcp
fastcgi.spawn: "php-fpm"
"${DOMAIN}:443":
listen:
port: 443
ssl:
certificate-file: /etc/ssl/certs/${DOMAIN}.crt
key-file: /etc/ssl/private/${DOMAIN}.key
cipher-suite: "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256"
cipher-preference: server
paths:
"/":
file.dir: /var/www/html
file.index: ['index.html', 'index.php']
fastcgi.connect:
port: 9000
type: tcp
fastcgi.spawn: "php-fpm"
header.add: "Strict-Transport-Security: max-age=31536000; includeSubDomains"
header.add: "X-Frame-Options: DENY"
header.add: "X-Content-Type-Options: nosniff"
EOF
chmod 644 /etc/h2o/h2o.conf
chown root:root /etc/h2o/h2o.conf
# Create test files
cat > /var/www/html/index.html << EOF
<!DOCTYPE html>
<html><head><title>H2O Server</title></head>
<body><h1>H2O HTTP/2 Server Running!</h1><p>Domain: ${DOMAIN}</p></body></html>
EOF
cat > /var/www/html/info.php << EOF
<?php phpinfo(); ?>
EOF
chown $APACHE_USER:$APACHE_USER /var/www/html/index.html /var/www/html/info.php
chmod 644 /var/www/html/index.html /var/www/html/info.php
# [9/10] Create systemd service
echo -e "${BLUE}[9/10] Creating systemd service...${NC}"
cat > /etc/systemd/system/h2o.service << EOF
[Unit]
Description=H2O HTTP/2 Web Server
Documentation=https://h2o.examp1e.net/
After=network.target
[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/h2o -c /etc/h2o/h2o.conf
ExecReload=/bin/kill -HUP \$MAINPID
KillMode=mixed
StandardOutput=journal
StandardError=journal
SyslogIdentifier=h2o
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable h2o
systemctl start h2o
# Configure firewall
if command -v ufw &> /dev/null && ufw status | grep -q "Status: active"; then
ufw allow 80/tcp
ufw allow 443/tcp
log_success "UFW firewall rules added"
elif command -v firewall-cmd &> /dev/null; then
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=443/tcp
firewall-cmd --reload
log_success "Firewalld rules added"
fi
# [10/10] Verify installation
echo -e "${BLUE}[10/10] Verifying installation...${NC}"
# Check if H2O is running
if systemctl is-active --quiet h2o; then
log_success "H2O service is running"
else
log_error "H2O service failed to start"
systemctl status h2o
exit 1
fi
# Check if PHP-FPM is running
if systemctl is-active --quiet $PHP_FPM_SERVICE; then
log_success "PHP-FPM service is running"
else
log_warning "PHP-FPM service not running properly"
fi
# Check if ports are listening
if ss -tlnp | grep -q ":80 "; then
log_success "HTTP port 80 is listening"
else
log_warning "HTTP port 80 is not listening"
fi
if ss -tlnp | grep -q ":443 "; then
log_success "HTTPS port 443 is listening"
else
log_warning "HTTPS port 443 is not listening"
fi
echo ""
log_success "H2O installation completed successfully!"
echo ""
echo "Configuration details:"
echo " - Domain: $DOMAIN"
echo " - HTTP: http://$DOMAIN"
echo " - HTTPS: https://$DOMAIN"
echo " - Document root: /var/www/html"
echo " - Config file: /etc/h2o/h2o.conf"
echo " - Log files: /var/log/h2o/"
echo " - SSL certificate: /etc/ssl/certs/${DOMAIN}.crt"
echo ""
echo "Test URLs:"
echo " - Static content: http://$DOMAIN/"
echo " - PHP info: http://$DOMAIN/info.php"
echo ""
log_warning "Self-signed SSL certificate is for testing only. Use Let's Encrypt for production."
Review the script before running. Execute with: bash install.sh