⚠️ Version Policy
H2O no longer uses version tagging since January 2020. The master branch is considered stable. Build from source for latest security patches.
⚠️ Debian Package Warning
Debian packages have 5 unpatched CVEs. Build from source for secure deployments.
This guide provides hardening measures for H2O deployments. H2O is a modern web server with native HTTP/3 (QUIC) support, requiring specific security considerations.
┌─────────────────────────────────────────────────────────────┐
│ Internet │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: Firewall Rules │
│ - TCP 80, 443 │
│ - UDP 443 (HTTP/3 QUIC - optional) │
│ - Rate limiting │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 2: H2O Server (Hardened) │
│ - TLS 1.3 │
│ - Security headers │
│ - Rate limiting │
│ - Access logging │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 3: Backend Services │
│ - Localhost only │
│ - Private network │
└─────────────────────────────────────────────────────────────┘
UFW (Ubuntu/Debian):
# Default deny
sudo ufw default deny incoming
# Allow HTTP/HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Allow HTTP/3 QUIC (optional)
sudo ufw allow 443/udp
# Enable firewall
sudo ufw enable
firewalld (RHEL/CentOS):
# Add services
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
# Add QUIC port if using HTTP/3
sudo firewall-cmd --permanent --add-port=443/udp
# Apply changes
sudo firewall-cmd --reload
listen:
host: 0.0.0.0 # All interfaces (default)
port: 80
# Or bind to specific IP
listen:
host: 192.168.1.10
port: 80
listen:
port: 443
ssl:
certificate-file: /etc/ssl/certs/example.com.crt
key-file: /etc/ssl/private/example.com.key
http3: true # Enable only if QUIC benefits your use case
⚠️ Note: HTTP/3 requires UDP port 443. Test thoroughly before enabling in production.
listen:
port: 443
ssl:
certificate-file: /etc/ssl/certs/example.com.crt
key-file: /etc/ssl/private/example.com.key
dh-file: /etc/ssl/dhparam.pem
minimum-version: TLSv1.2
cipher-preference: server
# Generate DH parameters
# openssl dhparam -out /etc/ssl/dhparam.pem 2048
hosts:
"example.com":
header.add:
strict-transport-security: "max-age=31536000; includeSubDomains; preload"
x-content-type-options: "nosniff"
x-frame-options: "SAMEORIGIN"
x-xss-protection: "1; mode=block"
content-security-policy: "default-src 'self'"
referrer-policy: "strict-origin-when-cross-origin"
permissions-policy: "geolocation=(), microphone=(), camera=()"
server-name: false
# Remove sample configurations
sudo rm -f /etc/h2o/*.sample
sudo rm -rf /var/www/html/*
# Create minimal index.html
echo "<!DOCTYPE html><html><head><title>Server</title></head><body><h1>Running</h1></body></html>" | sudo tee /var/www/html/index.html
# Configuration files
sudo chown root:root /etc/h2o/h2o.conf
sudo chmod 644 /etc/h2o/h2o.conf
# SSL private keys
sudo chown root:root /etc/ssl/private/*.key
sudo chmod 600 /etc/ssl/private/*.key
# Log directory
sudo chown h2o:adm /var/log/h2o
sudo chmod 755 /var/log/h2o
# Create dedicated user
sudo useradd -r -s /usr/sbin/nologin h2o
# Update systemd service
sudo systemctl edit h2o
Add:
[Service]
User=h2o
Group=h2o
Create systemd override:
sudo systemctl edit h2o
Add security hardening:
[Service]
# Filesystem protection
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=true
ReadWritePaths=/var/log/h2o /var/lib/h2o
# Network restrictions
RestrictAddressFamilies=AF_INET AF_INET6
# Capability restrictions
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=
# System call filtering
SystemCallArchitectures=native
SystemCallFilter=@system-service
# Memory protection
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
# Restrict privileges
NoNewPrivileges=true
RestrictSUIDSGID=true
# Resource limits
LimitNOFILE=65535
CPUQuota=50%
MemoryLimit=512M
Since H2O doesn’t use version tagging, update regularly from master:
cd /usr/local/src/h2o
git pull
cd build
make
sudo make install
sudo systemctl restart h2o
filter:
- name: rate-limit
config:
burst: 100
rate: 50
burst-delay: 100ms
delay: 20ms
max-request-entity-size: 10485760 # 10MB
http2:
max-concurrent-streams: 100
idle-timeout: 30s
http2:
idle-timeout: 30s
max-concurrent-streams: 100
http3:
idle-timeout: 30s
max-concurrent-streams: 100
hosts:
"example.com":
paths:
"/":
file.dir: /var/www/html
# Use mruby to restrict methods if needed
access-log:
path: /var/log/h2o/access.log
format: '%v:%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"'
error-log: /var/log/h2o/error.log
log-level: warn
# /etc/logrotate.d/h2o
/var/log/h2o/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 0640 h2o adm
postrotate
systemctl reload h2o
endscript
}
Forward logs to central logging system:
# /etc/rsyslog.d/h2o.conf
:programname, isequal, "h2o" /var/log/central/h2o.log
listen:
port: 443
ssl:
certificate-file: /etc/ssl/certs/example.com.crt
key-file: /etc/ssl/private/example.com.key
http3: true
| Setting | Recommendation |
|---|---|
| 0-RTT | Disable for sensitive applications (replay attack risk) |
| Connection Migration | Validate migration tokens |
| UDP Rate Limiting | Implement to prevent amplification |
| Certificate Validation | Ensure proper certificate chain |
sudo h2o -t -c /etc/h2o/h2o.conf
# Check running user
ps aux | grep h2o
# Check open ports
sudo netstat -tlnp | grep h2o
sudo netstat -ulnp | grep h2o # UDP for QUIC
# Check file permissions
ls -la /etc/h2o/
ls -la /var/log/h2o/
# Check systemd hardening
systemctl show h2o | grep -E "Protect|Private|Restrict"
# Port scan (from external host)
nmap -sV -sC SERVER_IP
# SSL test
testssl.sh SERVER_IP:443
# HTTP/2 test
h2c -n 100 https://SERVER_IP/