The HTTP bottleneck that was killing our ecommerce performance
When checkout abandonment jumps from 18% to 31% during peak traffic, you know something's fundamentally broken. A European fashion retailer came to us with exactly this problem, processing €2.8M monthly but hemorrhaging conversions every evening and weekend.
Their infrastructure looked solid: Nginx load balancers, horizontal scaling, Redis sessions, optimized MySQL. But page loads were crawling from 2.1 seconds to 8-12 seconds under load.
Turns out the issue wasn't their servers. It was HTTP/1.1.
The real culprit: protocol-level bottlenecks
During our audit, we found the smoking gun. Product pages were loading 47 assets on average, but HTTP/1.1 only allows 6-8 concurrent connections per domain. Every page load meant connection queuing, with head-of-line blocking adding 1.2-3.4 seconds of pure wait time.
The math was brutal:
- 12-15 TCP connections per page
- 800ms just for connection establishment during peak traffic
- CPU usage reasonable, but connection pools maxed out constantly
This wasn't a capacity problem; it was an efficiency problem.
The fix: HTTP/2 then HTTP/3 migration
Instead of throwing more servers at the problem, we upgraded the protocol layer. HTTP/2 eliminates head-of-line blocking through multiplexing, while HTTP/3 takes it further by running over QUIC instead of TCP.
HTTP/2 implementation
We upgraded to Nginx 1.25.1 with specific tuning for their asset-heavy pages:
http2_max_concurrent_streams 256;
http2_chunk_size 8k;
http2_body_preread_size 64k;
http2_idle_timeout 60s;
Key insight: we bumped concurrent streams to 256 because their pages averaged 47 assets. Default settings weren't cutting it.
HTTP/3 configuration
Next, we compiled Nginx with BoringSSL and enabled QUIC:
listen 443 quic reuseport;
http3 on;
http3_hq on;
add_header Alt-Svc 'h3=":443"; ma=86400';
Asset strategy overhaul
Here's the counterintuitive part: we actually split their bundled assets. Under HTTP/1.1, they were concatenating CSS and JS to reduce requests. With HTTP/2 multiplexing, this hurts performance.
We broke their monolithic CSS into 6 targeted files and split JavaScript into critical/non-critical modules. This enabled selective loading and better caching.
Results that actually matter
HTTP/2 phase:
- Peak load times: 8.2s → 4.8s (41% improvement)
- 95th percentile: 12.3s → 6.4s
- Checkout abandonment: 31% → 23%
- Estimated revenue impact: +€47k/month
HTTP/3 final results:
- Total improvement: 47% reduction in page load times
- Connection count per page: 14 → 2.1
- CPU usage on load balancers: -18%
- Memory for connection tracking: -28%
What I'd do differently
- Implement HTTP/3 push priorities more aggressively from day one
- Plan more time for asset bundling analysis (optimal strategy varies significantly)
- Set up protocol-level monitoring earlier (stream utilization and connection reuse patterns are crucial)
- Consolidate subdomains to improve connection coalescing
The biggest lesson? Sometimes the bottleneck isn't where you think it is. Before scaling out, look at the protocol layer. HTTP/1.1 is often the hidden constraint in modern web applications.
Originally published on binadit.com
Top comments (0)