Why Migrate?
HTTP/2 delivers measurable performance improvements for most production workloads:
- Reduced latency — multiplexing eliminates per-request connection overhead
- Lower bandwidth — HPACK header compression reduces header overhead by 85–95%
- Fewer TCP connections — one connection per origin vs six in HTTP/1.1
- No code changes required — the protocol is semantically identical to HTTP/1.1; existing REST APIs, cookies, and headers work unchanged
Real-world benchmarks show 15–30% improvement in page load time for asset-heavy applications. API endpoints with many small requests benefit most from multiplexing.
Prerequisites: TLS and ALPN
HTTP/2 over TLS (h2) requires TLS 1.2+ and the ALPN extension (Application-Layer Protocol Negotiation, RFC 7301). ALPN allows the client and server to agree on HTTP/2 during the TLS handshake without an extra round trip.
TLS ClientHello → ALPN extension: ['h2', 'http/1.1']
TLS ServerHello → ALPN extension: 'h2'
(TLS handshake complete — HTTP/2 selected)
HTTP/2 cleartext (h2c) exists but browsers do not support it. For all practical purposes, HTTP/2 requires HTTPS. If you don't already have TLS, set it up first with Let's Encrypt or your CDN.
Server Configuration
Nginx
server {
listen 443 ssl http2; # Add 'http2' to the listen directive
listen [::]:443 ssl http2; # IPv6 too
ssl_certificate /etc/ssl/certs/example.com.pem;
ssl_certificate_key /etc/ssl/private/example.com.key;
# TLS 1.2+ required for HTTP/2
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# Optional: server push
http2_push /styles.css;
}
Apache (mod_http2)
LoadModule http2_module modules/mod_http2.so
<VirtualHost *:443>
Protocols h2 h2c http/1.1
SSLEngine on
...
</VirtualHost>
Caddy
Caddy enables HTTP/2 (and HTTP/3) automatically with automatic HTTPS. No additional configuration is required:
example.com {
reverse_proxy localhost:8000
# HTTP/2 and HTTPS enabled automatically
}
Client Compatibility
HTTP/2 client support is excellent:
| Client | HTTP/2 Support |
|---|---|
| Chrome / Edge | Since 2015 |
| Firefox | Since 2015 |
| Safari | Since 2015 |
| curl | Since 7.43 (with nghttp2) |
| Python httpx | Default for HTTPS |
| Python requests | No (HTTP/1.1 only) |
| Node.js http2 | Built-in since Node 8 |
Clients that do not support HTTP/2 automatically fall back to HTTP/1.1 via ALPN negotiation — backward compatibility is guaranteed.
Testing the Migration
# Verify HTTP/2 with curl
curl -I --http2 https://api.example.com/
# Look for: HTTP/2 200
# Show ALPN negotiation details
curl -v --http2 https://api.example.com/ 2>&1 | grep -E 'ALPN|HTTP/'
# Chrome DevTools → Network → Protocol column → 'h2'
Performance Gains
Measure before and after with consistent methodology:
# Benchmark with wrk (HTTP/2 requires wrk2 or h2load)
h2load -n 10000 -c 100 -m 10 https://api.example.com/
# -m 10: 10 concurrent streams per connection
Expect the biggest gains on endpoints that make multiple parallel subrequests. A page loading 50 assets via HTTP/1.1 (6 parallel connections × multiple round trips) vs HTTP/2 (1 connection, 50 concurrent streams) is a dramatic difference.
Common Pitfalls
1. Domain sharding (anti-pattern): HTTP/1.1 apps often split assets across multiple subdomains (cdn1.example.com, cdn2.example.com) to bypass the 6-connection limit. With HTTP/2, this creates multiple connections instead of one. Remove domain sharding when migrating.
2. Unnecessary resource inlining: HTTP/1.1 performance tips recommend inlining small CSS/JS to avoid round trips. With HTTP/2 multiplexing, separate files are preferable — they can be cached independently.
3. Load balancer HTTP/2 support: Many load balancers terminate HTTP/2 from the client and use HTTP/1.1 to backends — this is fine and common. However, confirm your LB supports HTTP/2 on the frontend.
Monitoring
After deployment, track the protocol distribution in access logs:
log_format main '$remote_addr - [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $server_protocol';
# server_protocol: 'HTTP/2.0' or 'HTTP/1.1'
Expect 85–95% of modern browser traffic to negotiate HTTP/2. Residual HTTP/1.1 traffic is typically bots, older mobile apps, and server-to-server calls that haven't been updated.